From 2249ac1bbc0618d629186d4e90960a4892de9bcf Mon Sep 17 00:00:00 2001 From: Phil Ngo <58080811+philknows@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:59:05 -0400 Subject: [PATCH 01/55] chore: modify license structure to include apache2 (#6835) * Update and rename LICENSE to LICENSE-LGPL * Create LICENSE-APACHE --- LICENSE-APACHE | 204 ++++++++++++++++++++++++++++++++++++++++ LICENSE => LICENSE-LGPL | 7 ++ 2 files changed, 211 insertions(+) create mode 100644 LICENSE-APACHE rename LICENSE => LICENSE-LGPL (98%) diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000000..b45ca95e4811 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,204 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + License applies to: + - lodestar/packages/api + - lodestar/packages/config + - lodestar/packages/db + - lodestar/packages/flare + - lodestar/packages/fork-choice + - lodestar/packages/light-client + - lodestar/packages/logger + - lodestar/packages/params + - lodestar/packages/reqresp + - lodestar/packages/spec-test-util + - lodestar/packages/state-transition + - lodestar/packages/test-utils + - lodestar/packages/types + - lodestar/packages/utils + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE b/LICENSE-LGPL similarity index 98% rename from LICENSE rename to LICENSE-LGPL index 0a041280bd00..10747d348e85 100644 --- a/LICENSE +++ b/LICENSE-LGPL @@ -163,3 +163,10 @@ whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. + +END OF TERMS AND CONDITIONS + +License applies to: +- lodestar/packages/beacon-node +- lodestar/packages/cli +- lodestar/packages/validator From 3a9efd2c4ab8dd8e00d49a1ede7219f887fe190e Mon Sep 17 00:00:00 2001 From: Cayman Date: Tue, 4 Jun 2024 20:10:10 -0400 Subject: [PATCH 02/55] chore: bump @chainsafe/blst to v0.2.11 (#6856) --- packages/beacon-node/package.json | 2 +- packages/cli/package.json | 2 +- packages/state-transition/package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 9c059afdc5a7..c71391ae9bfc 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -96,7 +96,7 @@ "dependencies": { "@chainsafe/as-sha256": "^0.4.1", "@chainsafe/bls": "7.1.3", - "@chainsafe/blst": "^0.2.10", + "@chainsafe/blst": "^0.2.11", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", "@chainsafe/libp2p-gossipsub": "^13.0.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index aa0eb0215513..393af1bf0fd4 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -54,7 +54,7 @@ "@chainsafe/bls": "7.1.3", "@chainsafe/bls-keygen": "^0.4.0", "@chainsafe/bls-keystore": "^3.0.1", - "@chainsafe/blst": "^0.2.10", + "@chainsafe/blst": "^0.2.11", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", "@chainsafe/persistent-merkle-tree": "^0.7.1", diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index 121ab68e7dd6..92939bff8fdc 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -60,7 +60,7 @@ "dependencies": { "@chainsafe/as-sha256": "^0.4.1", "@chainsafe/bls": "7.1.3", - "@chainsafe/blst": "^0.2.10", + "@chainsafe/blst": "^0.2.11", "@chainsafe/persistent-merkle-tree": "^0.7.1", "@chainsafe/persistent-ts": "^0.19.1", "@chainsafe/ssz": "^0.15.1", diff --git a/yarn.lock b/yarn.lock index 7e0e075663a8..19f39c58ea4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -306,10 +306,10 @@ "@chainsafe/bls-keygen" "^0.4.0" bls-eth-wasm "^0.4.8" -"@chainsafe/blst@^0.2.10": - version "0.2.10" - resolved "https://registry.yarnpkg.com/@chainsafe/blst/-/blst-0.2.10.tgz#77802e5b1ff2d98ec1d25dcd5f7d27b89d376a40" - integrity sha512-ofecTL5fWsNwnpS2oUh56dDXJRmCEcDKNNBFDb2ux+WtvdjrdSq6B+L/eNlg+sVBzXbzrCw1jq8Y8+cYiHg32w== +"@chainsafe/blst@^0.2.11": + version "0.2.11" + resolved "https://registry.yarnpkg.com/@chainsafe/blst/-/blst-0.2.11.tgz#5ec85cd663592819d1dc51127e75dfd834250e3d" + integrity sha512-URyOLq5GtxBoxibOnd2pgLydCy0UZzbiIIBcsRAvGxAsRzjZL04TsQfwRkz5aphU3a1ebeRoMmI/HHyMCiFSQg== dependencies: "@types/tar" "^6.1.4" node-fetch "^2.6.1" From def34eb3428286bfce8de7d7df4567837b54a2c3 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 5 Jun 2024 05:04:00 -0700 Subject: [PATCH 03/55] chore: improve vitest output (#6850) * chore: improve vitest output * chore: address comments * chore: address comments --- docs/pages/contribution/testing/index.md | 8 +++++--- vitest.base.unit.config.ts | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/pages/contribution/testing/index.md b/docs/pages/contribution/testing/index.md index fd1f8e47a6e4..ecef1ef22c56 100644 --- a/docs/pages/contribution/testing/index.md +++ b/docs/pages/contribution/testing/index.md @@ -4,9 +4,11 @@ Testing is critical to the Lodestar project and there are many types of tests th There are few flags you can set through env variables to override behavior of testing and it's output. -| ENV variable | Effect | Impact | -| ----------------- | ------ | ----------------------------------------------------------------------------------------------------------- | -| TEST_COMPACT_DIFF | All | Will strip down the object difference rendered during test failures. Very useful for large object matching. | +| ENV variable | Effect | Impact | +| ------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------- | +| TEST_COMPACT_DIFF | All | Will strip down the object difference rendered during test failures. Very useful for large object matching. | +| TEST_QUIET_CONSOLE | All | Will strip down console output. Reduce console flickering. | +| TEST_COMPACT_OUTPUT | All | Display a condensed summary of tests run. Use vitest [basic](https://vitest.dev/guide/reporters#basic-reporter) reporter. | ### Unit Tests diff --git a/vitest.base.unit.config.ts b/vitest.base.unit.config.ts index cc123e81dffd..89d40a3d3247 100644 --- a/vitest.base.unit.config.ts +++ b/vitest.base.unit.config.ts @@ -22,7 +22,7 @@ export default defineConfig({ ], reporters: process.env.GITHUB_ACTIONS ? ["verbose", "hanging-process", "github-actions"] - : ["verbose", "hanging-process"], + : [process.env.TEST_COMPACT_OUTPUT ? "basic" : "verbose", "hanging-process"], coverage: { enabled: process.env.CI === "true", clean: true, @@ -46,5 +46,6 @@ export default defineConfig({ ], }, diff: process.env.TEST_COMPACT_DIFF ? path.join(import.meta.dirname, "./scripts/vitest/vitest.diff.ts") : undefined, + onConsoleLog: () => !process.env.TEST_QUIET_CONSOLE, }, }); From 49c16890a97cc44ea280acfae6712913e5091879 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 5 Jun 2024 06:39:44 -0700 Subject: [PATCH 04/55] chore: add new FAQ about yarn version (#6796) * chore: add new FAQ about yarn version * chore address comments * chore: address comments * Update CONTRIBUTING.md Co-authored-by: Nico Flaig * chore: address comments * chore: address comments * Update docs/pages/faqs.md Co-authored-by: Nico Flaig * Update docs/pages/faqs.md Co-authored-by: Nico Flaig * chore: address comments * chore: address comments * fix: typo --------- Co-authored-by: Nico Flaig --- .wordlist.txt | 1 + CONTRIBUTING.md | 1 + docs/pages/faqs.md | 14 ++++++++++++++ package.json | 1 + 4 files changed, 17 insertions(+) diff --git a/.wordlist.txt b/.wordlist.txt index 64d72550755b..06622ba04b3e 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -13,6 +13,7 @@ Casper Chai ChainSafe Codespaces +Corepack Customizations DPoS DVs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 026a72298a57..903f9ee32da7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,7 @@ When using MacOS, there are a couple of extra prerequisites that are required. ## Getting Started +- :gear: Run `corepack enable` to enable [Corepack](https://nodejs.org/api/corepack.html). - :gear: Run `yarn` to install dependencies. - :gear: Run `yarn build` to build lib from source. - :package: A `lodestar` binary will be bundled in `./packages/cli/bin`. diff --git a/docs/pages/faqs.md b/docs/pages/faqs.md index d86f2a37471e..8c6351a2438d 100644 --- a/docs/pages/faqs.md +++ b/docs/pages/faqs.md @@ -2,6 +2,20 @@ This section of the documentation will cover common questions and encounters often asked by users and developers. +## Tooling + +:::note "Package manager issues" + +Lodestar relies on [Corepack](https://nodejs.org/api/corepack.html) and associated `packageManager` value to manage its package manager version. + +Make sure `corepack` is correctly enabled if you encounter some package manager related issues: + +```bash +corepack enable +``` + +::: + ## Troubleshooting Lodestar ### Running a beacon node diff --git a/package.json b/package.json index 96e4b6166ca7..02e285e0c86a 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "engines": { "node": ">=20.1.0 <21 || >=22 <23" }, + "packageManager": "yarn@1.22.22+sha256.c17d3797fb9a9115bf375e31bfd30058cac6bc9c3b8807a3d8cb2094794b51ca", "workspaces": [ "packages/*" ], From 966f3acc0f615b62b082176412e85f7dbbb8d696 Mon Sep 17 00:00:00 2001 From: Julien Date: Fri, 7 Jun 2024 06:04:50 -0700 Subject: [PATCH 05/55] docs: introduce new documentation layout (#6860) * docs: introduce new layout * fix: incorrect paths * chore: added binaries section * fix: broken links * chore: cleanup * chore: setup redirects * chore: added missing dependency * chore: upgraded docusaurus * chore: remove duplicated page * chore: lints --- .github/workflows/docs.yml | 3 - docs/docusaurus.config.ts | 28 + docs/package.json | 11 +- .../advanced-topics/block-exploration.md | 0 .../advanced-topics/doppelganger-detection.md | 0 .../migrating-from-other-clients.md | 0 .../advanced-topics/setting-up-a-testnet.md | 0 .../advanced-topics/slashing-protection.md | 0 .../{ => contribution}/tools/core-dumps.md | 0 .../{ => contribution}/tools/debugging.md | 0 .../{ => contribution}/tools/flamegraphs.md | 0 .../{ => contribution}/tools/heap-dumps.md | 0 docs/pages/{ => contribution}/tools/perf.md | 0 .../getting-started/starting-a-node.new.md | 21 - docs/pages/index.md | 8 +- .../{ => libraries}/api/using-the-api.md | 0 .../lightclient-prover/.gitkeep | 0 docs/pages/reference/cli.md | 8 +- .../beacon-management}/data-retention.md | 2 +- .../mev-and-builder-integration.md | 0 .../{ => run}/beacon-management/networking.md | 0 .../beacon-management}/starting-a-node.md | 0 .../{ => run}/beacon-management/syncing.md | 0 .../{ => run}/getting-started/installation.md | 6 +- .../{ => run}/getting-started/quick-start.md | 0 .../logging-and-metrics/client-monitoring.md | 0 .../logging-and-metrics/dashboards.md | 0 .../logging-and-metrics/log-management.md | 0 .../logging-and-metrics/metrics-management.md | 0 .../logging-and-metrics/prometheus-grafana.md | 0 .../validator-management/external-signer.md | 0 .../validator-management/key-management.md | 0 .../multiple-and-fallback-validation.md | 0 .../validator-management/vc-configuration.md | 2 +- .../validator-management/withdrawals.md | 0 docs/sidebars.ts | 121 +- docs/yarn.lock | 1860 +++++++++++++++-- packages/cli/src/cmds/beacon/index.ts | 2 +- packages/cli/src/cmds/bootnode/index.ts | 2 +- packages/cli/src/cmds/lightclient/index.ts | 2 +- packages/cli/src/cmds/validator/index.ts | 2 +- scripts/prepare-docs.sh | 4 +- 42 files changed, 1752 insertions(+), 330 deletions(-) rename docs/pages/{ => contribution}/advanced-topics/block-exploration.md (100%) rename docs/pages/{ => contribution}/advanced-topics/doppelganger-detection.md (100%) rename docs/pages/{ => contribution}/advanced-topics/migrating-from-other-clients.md (100%) rename docs/pages/{ => contribution}/advanced-topics/setting-up-a-testnet.md (100%) rename docs/pages/{ => contribution}/advanced-topics/slashing-protection.md (100%) rename docs/pages/{ => contribution}/tools/core-dumps.md (100%) rename docs/pages/{ => contribution}/tools/debugging.md (100%) rename docs/pages/{ => contribution}/tools/flamegraphs.md (100%) rename docs/pages/{ => contribution}/tools/heap-dumps.md (100%) rename docs/pages/{ => contribution}/tools/perf.md (100%) delete mode 100644 docs/pages/getting-started/starting-a-node.new.md rename docs/pages/{ => libraries}/api/using-the-api.md (100%) rename docs/pages/{ => libraries}/lightclient-prover/.gitkeep (100%) rename docs/pages/{ => run/beacon-management}/data-retention.md (97%) rename docs/pages/{ => run}/beacon-management/mev-and-builder-integration.md (100%) rename docs/pages/{ => run}/beacon-management/networking.md (100%) rename docs/pages/{getting-started => run/beacon-management}/starting-a-node.md (100%) rename docs/pages/{ => run}/beacon-management/syncing.md (100%) rename docs/pages/{ => run}/getting-started/installation.md (93%) rename docs/pages/{ => run}/getting-started/quick-start.md (100%) rename docs/pages/{ => run}/logging-and-metrics/client-monitoring.md (100%) rename docs/pages/{ => run}/logging-and-metrics/dashboards.md (100%) rename docs/pages/{ => run}/logging-and-metrics/log-management.md (100%) rename docs/pages/{ => run}/logging-and-metrics/metrics-management.md (100%) rename docs/pages/{ => run}/logging-and-metrics/prometheus-grafana.md (100%) rename docs/pages/{ => run}/validator-management/external-signer.md (100%) rename docs/pages/{ => run}/validator-management/key-management.md (100%) rename docs/pages/{ => run}/validator-management/multiple-and-fallback-validation.md (100%) rename docs/pages/{ => run}/validator-management/vc-configuration.md (99%) rename docs/pages/{ => run}/validator-management/withdrawals.md (100%) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b8f4e7b95eb0..63db55435edf 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -62,9 +62,6 @@ jobs: - name: Lint built docs run: yarn docs:lint:fix - - name: Set up Python - uses: actions/setup-python@v1 - - name: Build docs working-directory: docs run: yarn && yarn build diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 0479fe696170..e37cee619e81 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -28,6 +28,34 @@ const config: Config = { locales: ["en"], }, + plugins: [ + [ + "@docusaurus/plugin-client-redirects", + { + createRedirects(existingPath: string) { + if ( + existingPath.includes("/advanced-topics") || + existingPath.includes("/getting-started") || + existingPath.includes("/beacon-management") || + existingPath.includes("/validator-management") || + existingPath.includes("/logging-and-metrics") || + existingPath.includes("/bootnode") || + existingPath.includes("/data-retention") + ) { + return ["/run".concat(existingPath)]; + } else if (existingPath.includes("/testing") || existingPath.includes("/tools")) { + return ["/contribution".concat(existingPath)]; + } else if (existingPath.includes("/lightclient-prover")) { + return ["/libraries".concat(existingPath)]; + } else if (existingPath.includes("data-retention.md")) { + return ["/run/beacon-management".concat(existingPath)]; + } + return undefined; // Return a falsy value: no redirect created + }, + }, + ], + ], + presets: [ [ "classic", diff --git a/docs/package.json b/docs/package.json index cb75045f57b6..6159ba123215 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,9 +14,10 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "^3.2.0", - "@docusaurus/preset-classic": "^3.2.0", - "@docusaurus/theme-mermaid": "^3.2.0", + "@docusaurus/core": "^3.4.0", + "@docusaurus/plugin-client-redirects": "^3.4.0", + "@docusaurus/preset-classic": "^3.4.0", + "@docusaurus/theme-mermaid": "^3.4.0", "@easyops-cn/docusaurus-search-local": "^0.40.1", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", @@ -25,8 +26,8 @@ "react-dom": "^18.0.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.2.0", - "@docusaurus/types": "^3.2.0" + "@docusaurus/module-type-aliases": "^3.4.0", + "@docusaurus/types": "^3.4.0" }, "browserslist": { "production": [ diff --git a/docs/pages/advanced-topics/block-exploration.md b/docs/pages/contribution/advanced-topics/block-exploration.md similarity index 100% rename from docs/pages/advanced-topics/block-exploration.md rename to docs/pages/contribution/advanced-topics/block-exploration.md diff --git a/docs/pages/advanced-topics/doppelganger-detection.md b/docs/pages/contribution/advanced-topics/doppelganger-detection.md similarity index 100% rename from docs/pages/advanced-topics/doppelganger-detection.md rename to docs/pages/contribution/advanced-topics/doppelganger-detection.md diff --git a/docs/pages/advanced-topics/migrating-from-other-clients.md b/docs/pages/contribution/advanced-topics/migrating-from-other-clients.md similarity index 100% rename from docs/pages/advanced-topics/migrating-from-other-clients.md rename to docs/pages/contribution/advanced-topics/migrating-from-other-clients.md diff --git a/docs/pages/advanced-topics/setting-up-a-testnet.md b/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md similarity index 100% rename from docs/pages/advanced-topics/setting-up-a-testnet.md rename to docs/pages/contribution/advanced-topics/setting-up-a-testnet.md diff --git a/docs/pages/advanced-topics/slashing-protection.md b/docs/pages/contribution/advanced-topics/slashing-protection.md similarity index 100% rename from docs/pages/advanced-topics/slashing-protection.md rename to docs/pages/contribution/advanced-topics/slashing-protection.md diff --git a/docs/pages/tools/core-dumps.md b/docs/pages/contribution/tools/core-dumps.md similarity index 100% rename from docs/pages/tools/core-dumps.md rename to docs/pages/contribution/tools/core-dumps.md diff --git a/docs/pages/tools/debugging.md b/docs/pages/contribution/tools/debugging.md similarity index 100% rename from docs/pages/tools/debugging.md rename to docs/pages/contribution/tools/debugging.md diff --git a/docs/pages/tools/flamegraphs.md b/docs/pages/contribution/tools/flamegraphs.md similarity index 100% rename from docs/pages/tools/flamegraphs.md rename to docs/pages/contribution/tools/flamegraphs.md diff --git a/docs/pages/tools/heap-dumps.md b/docs/pages/contribution/tools/heap-dumps.md similarity index 100% rename from docs/pages/tools/heap-dumps.md rename to docs/pages/contribution/tools/heap-dumps.md diff --git a/docs/pages/tools/perf.md b/docs/pages/contribution/tools/perf.md similarity index 100% rename from docs/pages/tools/perf.md rename to docs/pages/contribution/tools/perf.md diff --git a/docs/pages/getting-started/starting-a-node.new.md b/docs/pages/getting-started/starting-a-node.new.md deleted file mode 100644 index b66e797b29ed..000000000000 --- a/docs/pages/getting-started/starting-a-node.new.md +++ /dev/null @@ -1,21 +0,0 @@ -# Starting a Node - -## Prerequisites - -### Creating a Client Communication JWT - -### Creating a Validator Keystore - -## Base Considerations - -### Execution Client - -### Beacon Node - -### Validator Client - -## Production Considerations - -### Ingress/Egress - -### Fail-Over diff --git a/docs/pages/index.md b/docs/pages/index.md index 9da182db0f67..7948cb9f8b57 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -10,11 +10,11 @@ title: Home ### Getting started -- Follow the installation method for [source install](./getting-started/installation.md/#build-from-source) or [Docker install](./getting-started/installation.md/#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). +- Install [binaries](./run/getting-started/installation.md/#binaries), follow the installation method for [source install](./run/getting-started/installation.md/#build-from-source) or [Docker install](./run/getting-started/installation.md/#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). - Use [Lodestar libraries](./supporting-libraries/index.md) in your next Ethereum Typescript project. -- Run a beacon node on [mainnet or a public testnet](./getting-started/starting-a-node.md). -- Utilize the whole stack by [starting a local testnet](./advanced-topics/setting-up-a-testnet.md). -- View the Lodestar [CLI commands and options](./beacon-management/beacon-cli.md) +- Run a beacon node on [mainnet or a public testnet](./run/beacon-management/starting-a-node.md). +- Utilize the whole stack by [starting a local testnet](./contribution/advanced-topics/setting-up-a-testnet.md). +- View the Lodestar [CLI commands and options](./run/beacon-management/beacon-cli.md) - Prospective contributors can read the [contributing section](./contribution/getting-started.md) to understand how we develop and test on Lodestar. - If you have questions [submit an issue](https://github.com/ChainSafe/lodestar/issues/new) or join us on [Discord](https://discord.gg/yjyvFRP)! - Please note our [security policy](./security.md). diff --git a/docs/pages/api/using-the-api.md b/docs/pages/libraries/api/using-the-api.md similarity index 100% rename from docs/pages/api/using-the-api.md rename to docs/pages/libraries/api/using-the-api.md diff --git a/docs/pages/lightclient-prover/.gitkeep b/docs/pages/libraries/lightclient-prover/.gitkeep similarity index 100% rename from docs/pages/lightclient-prover/.gitkeep rename to docs/pages/libraries/lightclient-prover/.gitkeep diff --git a/docs/pages/reference/cli.md b/docs/pages/reference/cli.md index 9bfd266aa51b..a3cc1d28714a 100644 --- a/docs/pages/reference/cli.md +++ b/docs/pages/reference/cli.md @@ -2,8 +2,8 @@ _**Welcome! This page has been moved. Please checkout our new docs layout from the Table of Contents! Below are some helpful links to the CLI pages that were split out from this original document**_ -- [Beacon Node CLI](../beacon-management/beacon-cli.md) -- [Validator CLI](../validator-management/validator-cli.md) -- [Bootnode CLI](../bootnode/bootnode-cli.md) -- [Light Client CLI](../lightclient-prover/lightclient-cli.md) +- [Beacon Node CLI](../run/beacon-management/beacon-cli.md) +- [Validator CLI](../run/validator-management/validator-cli.md) +- [Bootnode CLI](../run/bootnode/bootnode-cli.md) +- [Light Client CLI](../libraries/lightclient-prover/lightclient-cli.md) - [Dev CLI](../contribution/dev-cli.md) diff --git a/docs/pages/data-retention.md b/docs/pages/run/beacon-management/data-retention.md similarity index 97% rename from docs/pages/data-retention.md rename to docs/pages/run/beacon-management/data-retention.md index 6ccdda68af5e..57fa5dde0f55 100644 --- a/docs/pages/data-retention.md +++ b/docs/pages/run/beacon-management/data-retention.md @@ -49,6 +49,6 @@ Configuring your node to store and prune data is key to success. On average you `keystores`, `keystore-cache` and `peerstore` are not usually very large and are not expected to grow much during normal operation. -Logs can also become quite large so please check out the section on [log management](./logging-and-metrics/log-management.md) for more information. +Logs can also become quite large so please check out the section on [log management](../logging-and-metrics/log-management.md) for more information. There is really only one flag that is needed to manage the data for Lodestar, [`--dataDir`](./beacon-management/beacon-cli#--datadir). Other than that handling log management is really the heart of the data management story. Beacon node data is what it is. Depending on the execution client that is chosen, there may be flags to help with data storage growth but that is outside the scope of this document. diff --git a/docs/pages/beacon-management/mev-and-builder-integration.md b/docs/pages/run/beacon-management/mev-and-builder-integration.md similarity index 100% rename from docs/pages/beacon-management/mev-and-builder-integration.md rename to docs/pages/run/beacon-management/mev-and-builder-integration.md diff --git a/docs/pages/beacon-management/networking.md b/docs/pages/run/beacon-management/networking.md similarity index 100% rename from docs/pages/beacon-management/networking.md rename to docs/pages/run/beacon-management/networking.md diff --git a/docs/pages/getting-started/starting-a-node.md b/docs/pages/run/beacon-management/starting-a-node.md similarity index 100% rename from docs/pages/getting-started/starting-a-node.md rename to docs/pages/run/beacon-management/starting-a-node.md diff --git a/docs/pages/beacon-management/syncing.md b/docs/pages/run/beacon-management/syncing.md similarity index 100% rename from docs/pages/beacon-management/syncing.md rename to docs/pages/run/beacon-management/syncing.md diff --git a/docs/pages/getting-started/installation.md b/docs/pages/run/getting-started/installation.md similarity index 93% rename from docs/pages/getting-started/installation.md rename to docs/pages/run/getting-started/installation.md index cf53916e054c..b5ea6b74f5fb 100644 --- a/docs/pages/getting-started/installation.md +++ b/docs/pages/run/getting-started/installation.md @@ -1,5 +1,9 @@ # Installation +## Binaries + +Binaries can be downloaded from [this page](https://github.com/ChainSafe/lodestar/releases/) under the `Assets` section. + ## Docker Installation The [`chainsafe/lodestar`](https://hub.docker.com/r/chainsafe/lodestar) Docker Hub repository is maintained actively. It contains the `lodestar` CLI preinstalled. @@ -81,7 +85,7 @@ Lodestar should now be ready for use. ./lodestar --help ``` -See [Command Line Reference](./../reference/cli.md) for further information. +See [Command Line Reference](./../../reference/cli.md) for further information. ### Known Issues diff --git a/docs/pages/getting-started/quick-start.md b/docs/pages/run/getting-started/quick-start.md similarity index 100% rename from docs/pages/getting-started/quick-start.md rename to docs/pages/run/getting-started/quick-start.md diff --git a/docs/pages/logging-and-metrics/client-monitoring.md b/docs/pages/run/logging-and-metrics/client-monitoring.md similarity index 100% rename from docs/pages/logging-and-metrics/client-monitoring.md rename to docs/pages/run/logging-and-metrics/client-monitoring.md diff --git a/docs/pages/logging-and-metrics/dashboards.md b/docs/pages/run/logging-and-metrics/dashboards.md similarity index 100% rename from docs/pages/logging-and-metrics/dashboards.md rename to docs/pages/run/logging-and-metrics/dashboards.md diff --git a/docs/pages/logging-and-metrics/log-management.md b/docs/pages/run/logging-and-metrics/log-management.md similarity index 100% rename from docs/pages/logging-and-metrics/log-management.md rename to docs/pages/run/logging-and-metrics/log-management.md diff --git a/docs/pages/logging-and-metrics/metrics-management.md b/docs/pages/run/logging-and-metrics/metrics-management.md similarity index 100% rename from docs/pages/logging-and-metrics/metrics-management.md rename to docs/pages/run/logging-and-metrics/metrics-management.md diff --git a/docs/pages/logging-and-metrics/prometheus-grafana.md b/docs/pages/run/logging-and-metrics/prometheus-grafana.md similarity index 100% rename from docs/pages/logging-and-metrics/prometheus-grafana.md rename to docs/pages/run/logging-and-metrics/prometheus-grafana.md diff --git a/docs/pages/validator-management/external-signer.md b/docs/pages/run/validator-management/external-signer.md similarity index 100% rename from docs/pages/validator-management/external-signer.md rename to docs/pages/run/validator-management/external-signer.md diff --git a/docs/pages/validator-management/key-management.md b/docs/pages/run/validator-management/key-management.md similarity index 100% rename from docs/pages/validator-management/key-management.md rename to docs/pages/run/validator-management/key-management.md diff --git a/docs/pages/validator-management/multiple-and-fallback-validation.md b/docs/pages/run/validator-management/multiple-and-fallback-validation.md similarity index 100% rename from docs/pages/validator-management/multiple-and-fallback-validation.md rename to docs/pages/run/validator-management/multiple-and-fallback-validation.md diff --git a/docs/pages/validator-management/vc-configuration.md b/docs/pages/run/validator-management/vc-configuration.md similarity index 99% rename from docs/pages/validator-management/vc-configuration.md rename to docs/pages/run/validator-management/vc-configuration.md index d86edb465c1c..077d3f479139 100644 --- a/docs/pages/validator-management/vc-configuration.md +++ b/docs/pages/run/validator-management/vc-configuration.md @@ -1,5 +1,5 @@ --- -title: Configuration +title: Stake with a Validator Client --- # Validator Configuration diff --git a/docs/pages/validator-management/withdrawals.md b/docs/pages/run/validator-management/withdrawals.md similarity index 100% rename from docs/pages/validator-management/withdrawals.md rename to docs/pages/run/validator-management/withdrawals.md diff --git a/docs/sidebars.ts b/docs/sidebars.ts index c33dd65c3d0b..47825d48b47a 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -3,83 +3,94 @@ import type {SidebarsConfig} from "@docusaurus/plugin-content-docs"; const sidebars: SidebarsConfig = { tutorialSidebar: [ "index", - "introduction", - { - type: "doc", - label: "Security", - id: "security", - }, - { - type: "category", - label: "Getting Started", - items: ["getting-started/quick-start", "getting-started/installation", "getting-started/starting-a-node"], - }, - "data-retention", - { - type: "category", - label: "Beacon Node", - items: [ - "beacon-management/beacon-cli", - "beacon-management/networking", - "beacon-management/mev-and-builder-integration", - "beacon-management/syncing", - ], - }, { type: "category", - label: "Validator", + label: "Run A Node", items: [ - "validator-management/validator-cli", - "validator-management/vc-configuration", - "validator-management/external-signer", + "run/getting-started/quick-start", + "run/getting-started/installation", + { + type: "category", + label: "Beacon node", + collapsed: false, + items: [ + "run/beacon-management/starting-a-node", + "run/beacon-management/beacon-cli", + "run/beacon-management/data-retention", + "run/beacon-management/networking", + "run/beacon-management/mev-and-builder-integration", + "run/beacon-management/syncing", + ], + }, + { + type: "category", + label: "Validator Client", + collapsed: false, + items: [ + "run/validator-management/vc-configuration", + "run/validator-management/validator-cli", + "run/validator-management/external-signer", + ], + }, + { + type: "category", + label: "Logging and Metrics", + collapsed: false, + items: ["run/logging-and-metrics/prometheus-grafana", "run/logging-and-metrics/client-monitoring"], + }, + { + type: "category", + label: "Discv5 Bootnode", + collapsed: false, + items: ["run/bootnode/bootnode-cli"], + }, ], }, { type: "category", - label: "Bootnode", - items: ["bootnode/bootnode-cli"], - }, - { - type: "category", - label: "Light Client and Prover", + label: "Developer Tools", + collapsed: false, items: [ - "lightclient-prover/lightclient-cli", { - type: "doc", - label: "Light Client", - id: "lightclient-prover/lightclient", + type: "category", + label: "Lodestar Light Client", + items: ["libraries/lightclient-prover/lightclient-cli", "libraries/lightclient-prover/lightclient"], }, { - type: "doc", - label: "Prover", - id: "lightclient-prover/prover", + type: "category", + label: "Lodestar Light Prover", + items: ["libraries/lightclient-prover/prover"], }, ], }, - { - type: "category", - label: "Logging and Metrics", - items: ["logging-and-metrics/prometheus-grafana", "logging-and-metrics/client-monitoring"], - }, + "supporting-libraries/index", { type: "category", label: "Contributing", + collapsed: false, items: [ { - type: "doc", - label: "Getting Started", - id: "contribution/getting-started", + type: "category", + label: "Advanced Topics", + collapsed: false, + items: ["contribution/advanced-topics/setting-up-a-testnet"], }, "contribution/depgraph", { - type: "doc", - label: "Dev CLI Reference", - id: "contribution/dev-cli", + type: "category", + label: "Development Tools", + items: [ + "contribution/tools/debugging", + "contribution/tools/flamegraphs", + "contribution/tools/heap-dumps", + "contribution/tools/core-dumps", + ], }, { type: "category", label: "Testing", + collapsed: false, items: [ "contribution/testing/index", "contribution/testing/end-to-end-tests", @@ -91,16 +102,6 @@ const sidebars: SidebarsConfig = { }, ], }, - { - type: "category", - label: "Tools", - items: ["tools/debugging", "tools/flamegraphs", "tools/heap-dumps", "tools/core-dumps"], - }, - { - type: "category", - label: "Advanced Topics", - items: ["advanced-topics/setting-up-a-testnet"], - }, "faqs", ], }; diff --git a/docs/yarn.lock b/docs/yarn.lock index 4995c0c33376..ad6053d02f35 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -154,11 +154,24 @@ "@babel/highlight" "^7.23.4" chalk "^2.4.2" +"@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== +"@babel/compat-data@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" + integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== + "@babel/core@^7.19.6", "@babel/core@^7.23.3": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.0.tgz#56cbda6b185ae9d9bed369816a8f4423c5f2ff1b" @@ -180,6 +193,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.21.3": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" + integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helpers" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.23.3", "@babel/generator@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" @@ -190,6 +224,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" + integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== + dependencies: + "@babel/types" "^7.24.7" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" @@ -197,6 +241,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-annotate-as-pure@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" + integrity sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg== + dependencies: + "@babel/types" "^7.24.7" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" @@ -204,6 +255,14 @@ dependencies: "@babel/types" "^7.22.15" +"@babel/helper-builder-binary-assignment-operator-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz#37d66feb012024f2422b762b9b2a7cfe27c7fba3" + integrity sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" @@ -215,6 +274,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz#4eb6c4a80d6ffeac25ab8cd9a21b5dfa48d503a9" + integrity sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg== + dependencies: + "@babel/compat-data" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.6": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.0.tgz#fc7554141bdbfa2d17f7b4b80153b9b090e5d158" @@ -230,6 +300,21 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz#2eaed36b3a1c11c53bdf80d53838b293c52f5b3b" + integrity sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" @@ -239,6 +324,15 @@ regexpu-core "^5.3.1" semver "^6.3.1" +"@babel/helper-create-regexp-features-plugin@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz#be4f435a80dc2b053c76eeb4b7d16dd22cfc89da" + integrity sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + regexpu-core "^5.3.1" + semver "^6.3.1" + "@babel/helper-define-polyfill-provider@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz#465805b7361f461e86c680f1de21eaf88c25901b" @@ -250,11 +344,29 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" +"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" + integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + "@babel/helper-environment-visitor@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== +"@babel/helper-environment-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" + integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== + dependencies: + "@babel/types" "^7.24.7" + "@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" @@ -263,6 +375,14 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.23.0" +"@babel/helper-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" + integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" @@ -270,6 +390,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-hoist-variables@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" + integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== + dependencies: + "@babel/types" "^7.24.7" + "@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" @@ -277,6 +404,14 @@ dependencies: "@babel/types" "^7.23.0" +"@babel/helper-member-expression-to-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz#67613d068615a70e4ed5101099affc7a41c5225f" + integrity sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-module-imports@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" @@ -284,6 +419,14 @@ dependencies: "@babel/types" "^7.22.15" +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-module-transforms@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" @@ -295,6 +438,17 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" +"@babel/helper-module-transforms@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz#31b6c9a2930679498db65b685b1698bfd6c7daf8" + integrity sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" @@ -302,11 +456,23 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-optimise-call-expression@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" + integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A== + dependencies: + "@babel/types" "^7.24.7" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== +"@babel/helper-plugin-utils@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz#98c84fe6fe3d0d3ae7bfc3a5e166a46844feb2a0" + integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== + "@babel/helper-remap-async-to-generator@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" @@ -316,6 +482,15 @@ "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-wrap-function" "^7.22.20" +"@babel/helper-remap-async-to-generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz#b3f0f203628522713849d49403f1a414468be4c7" + integrity sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-wrap-function" "^7.24.7" + "@babel/helper-replace-supers@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" @@ -325,6 +500,15 @@ "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" +"@babel/helper-replace-supers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz#f933b7eed81a1c0265740edc91491ce51250f765" + integrity sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" @@ -332,6 +516,14 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" @@ -339,6 +531,14 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-skip-transparent-expression-wrappers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" + integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" @@ -346,21 +546,43 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-split-export-declaration@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" + integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== + dependencies: + "@babel/types" "^7.24.7" + "@babel/helper-string-parser@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== +"@babel/helper-string-parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" + integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== + "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + "@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== +"@babel/helper-validator-option@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" + integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== + "@babel/helper-wrap-function@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" @@ -370,6 +592,16 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.19" +"@babel/helper-wrap-function@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz#52d893af7e42edca7c6d2c6764549826336aae1f" + integrity sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw== + dependencies: + "@babel/helper-function-name" "^7.24.7" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helpers@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.0.tgz#a3dd462b41769c95db8091e49cfe019389a9409b" @@ -379,6 +611,14 @@ "@babel/traverse" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/helpers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.7.tgz#aa2ccda29f62185acb5d42fb4a3a1b1082107416" + integrity sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/highlight@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" @@ -388,11 +628,34 @@ chalk "^2.4.2" js-tokens "^4.0.0" +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.22.7", "@babel/parser@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz#26a3d1ff49031c53a97d03b604375f028746a9ac" integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg== +"@babel/parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" + integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz#fd059fd27b184ea2b4c7e646868a9a381bbc3055" + integrity sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a" @@ -400,6 +663,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz#468096ca44bbcbe8fcc570574e12eb1950e18107" + integrity sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz#f6652bb16b94f8f9c20c50941e16e9756898dc5d" @@ -409,6 +679,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-optional-chaining" "^7.23.3" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz#e4eabdd5109acc399b38d7999b2ef66fc2022f89" + integrity sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.7" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.7": version "7.23.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz#516462a95d10a9618f197d39ad291a9b47ae1d7b" @@ -417,6 +696,14 @@ "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz#71b21bb0286d5810e63a1538aa901c58e87375ec" + integrity sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" @@ -464,6 +751,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-import-assertions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz#2a0b406b5871a20a841240586b1300ce2088a778" + integrity sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-import-attributes@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz#992aee922cf04512461d7dae3ff6951b90a2dc06" @@ -471,6 +765,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz#b4f9ea95a79e6912480c4b626739f86a076624ca" + integrity sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -492,6 +793,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-jsx@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" + integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -555,6 +863,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-typescript@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" + integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" @@ -570,6 +885,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-arrow-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz#4f6886c11e423bd69f3ce51dbf42424a5f275514" + integrity sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-async-generator-functions@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz#9adaeb66fc9634a586c5df139c6240d41ed801ce" @@ -580,6 +902,16 @@ "@babel/helper-remap-async-to-generator" "^7.22.20" "@babel/plugin-syntax-async-generators" "^7.8.4" +"@babel/plugin-transform-async-generator-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz#7330a5c50e05181ca52351b8fd01642000c96cfd" + integrity sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-remap-async-to-generator" "^7.24.7" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-transform-async-to-generator@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz#d1f513c7a8a506d43f47df2bf25f9254b0b051fa" @@ -589,6 +921,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-remap-async-to-generator" "^7.22.20" +"@babel/plugin-transform-async-to-generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz#72a3af6c451d575842a7e9b5a02863414355bdcc" + integrity sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-remap-async-to-generator" "^7.24.7" + "@babel/plugin-transform-block-scoped-functions@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz#fe1177d715fb569663095e04f3598525d98e8c77" @@ -596,6 +937,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-block-scoped-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz#a4251d98ea0c0f399dafe1a35801eaba455bbf1f" + integrity sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-block-scoping@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz#b2d38589531c6c80fbe25e6b58e763622d2d3cf5" @@ -603,6 +951,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-block-scoping@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz#42063e4deb850c7bd7c55e626bf4e7ab48e6ce02" + integrity sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-class-properties@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz#35c377db11ca92a785a718b6aa4e3ed1eb65dc48" @@ -611,6 +966,14 @@ "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-class-properties@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz#256879467b57b0b68c7ddfc5b76584f398cd6834" + integrity sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-class-static-block@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz#2a202c8787a8964dd11dfcedf994d36bfc844ab5" @@ -620,6 +983,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" +"@babel/plugin-transform-class-static-block@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz#c82027ebb7010bc33c116d4b5044fbbf8c05484d" + integrity sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-transform-classes@^7.23.8": version "7.23.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz#d08ae096c240347badd68cdf1b6d1624a6435d92" @@ -634,6 +1006,20 @@ "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" +"@babel/plugin-transform-classes@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz#4ae6ef43a12492134138c1e45913f7c46c41b4bf" + integrity sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz#652e69561fcc9d2b50ba4f7ac7f60dcf65e86474" @@ -642,6 +1028,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/template" "^7.22.15" +"@babel/plugin-transform-computed-properties@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz#4cab3214e80bc71fae3853238d13d097b004c707" + integrity sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/template" "^7.24.7" + "@babel/plugin-transform-destructuring@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz#8c9ee68228b12ae3dff986e56ed1ba4f3c446311" @@ -649,6 +1043,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-destructuring@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz#a097f25292defb6e6cc16d6333a4cfc1e3c72d9e" + integrity sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-dotall-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz#3f7af6054882ede89c378d0cf889b854a993da50" @@ -657,6 +1058,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-dotall-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz#5f8bf8a680f2116a7207e16288a5f974ad47a7a0" + integrity sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-duplicate-keys@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz#664706ca0a5dfe8d066537f99032fc1dc8b720ce" @@ -664,6 +1073,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-duplicate-keys@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz#dd20102897c9a2324e5adfffb67ff3610359a8ee" + integrity sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-dynamic-import@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz#c7629e7254011ac3630d47d7f34ddd40ca535143" @@ -672,6 +1088,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" +"@babel/plugin-transform-dynamic-import@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz#4d8b95e3bae2b037673091aa09cd33fecd6419f4" + integrity sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz#ea0d978f6b9232ba4722f3dbecdd18f450babd18" @@ -680,6 +1104,14 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-exponentiation-operator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz#b629ee22645f412024297d5245bce425c31f9b0d" + integrity sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-export-namespace-from@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz#084c7b25e9a5c8271e987a08cf85807b80283191" @@ -688,6 +1120,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" +"@babel/plugin-transform-export-namespace-from@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz#176d52d8d8ed516aeae7013ee9556d540c53f197" + integrity sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-transform-for-of@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e" @@ -696,6 +1136,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" +"@babel/plugin-transform-for-of@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz#f25b33f72df1d8be76399e1b8f3f9d366eb5bc70" + integrity sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-transform-function-name@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz#8f424fcd862bf84cb9a1a6b42bc2f47ed630f8dc" @@ -705,6 +1153,15 @@ "@babel/helper-function-name" "^7.23.0" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz#6d8601fbffe665c894440ab4470bc721dd9131d6" + integrity sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w== + dependencies: + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-json-strings@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz#a871d9b6bd171976efad2e43e694c961ffa3714d" @@ -713,6 +1170,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" +"@babel/plugin-transform-json-strings@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz#f3e9c37c0a373fee86e36880d45b3664cedaf73a" + integrity sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-transform-literals@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz#8214665f00506ead73de157eba233e7381f3beb4" @@ -720,6 +1185,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz#36b505c1e655151a9d7607799a9988fc5467d06c" + integrity sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-logical-assignment-operators@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz#e599f82c51d55fac725f62ce55d3a0886279ecb5" @@ -728,6 +1200,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" +"@babel/plugin-transform-logical-assignment-operators@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz#a58fb6eda16c9dc8f9ff1c7b1ba6deb7f4694cb0" + integrity sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-transform-member-expression-literals@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz#e37b3f0502289f477ac0e776b05a833d853cabcc" @@ -735,6 +1215,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-member-expression-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz#3b4454fb0e302e18ba4945ba3246acb1248315df" + integrity sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-modules-amd@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz#e19b55436a1416829df0a1afc495deedfae17f7d" @@ -743,6 +1230,14 @@ "@babel/helper-module-transforms" "^7.23.3" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-modules-amd@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz#65090ed493c4a834976a3ca1cde776e6ccff32d7" + integrity sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg== + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-modules-commonjs@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz#661ae831b9577e52be57dd8356b734f9700b53b4" @@ -752,6 +1247,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" +"@babel/plugin-transform-modules-commonjs@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz#9fd5f7fdadee9085886b183f1ad13d1ab260f4ab" + integrity sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ== + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/plugin-transform-modules-systemjs@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz#105d3ed46e4a21d257f83a2f9e2ee4203ceda6be" @@ -762,6 +1266,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.20" +"@babel/plugin-transform-modules-systemjs@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz#f8012316c5098f6e8dee6ecd58e2bc6f003d0ce7" + integrity sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw== + dependencies: + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/plugin-transform-modules-umd@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz#5d4395fccd071dfefe6585a4411aa7d6b7d769e9" @@ -770,6 +1284,14 @@ "@babel/helper-module-transforms" "^7.23.3" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-modules-umd@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz#edd9f43ec549099620df7df24e7ba13b5c76efc8" + integrity sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A== + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" @@ -778,6 +1300,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz#9042e9b856bc6b3688c0c2e4060e9e10b1460923" + integrity sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-new-target@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz#5491bb78ed6ac87e990957cea367eab781c4d980" @@ -785,6 +1315,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-new-target@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz#31ff54c4e0555cc549d5816e4ab39241dfb6ab00" + integrity sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz#45556aad123fc6e52189ea749e33ce090637346e" @@ -793,6 +1330,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz#1de4534c590af9596f53d67f52a92f12db984120" + integrity sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-transform-numeric-separator@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz#03d08e3691e405804ecdd19dd278a40cca531f29" @@ -801,6 +1346,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" +"@babel/plugin-transform-numeric-separator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz#bea62b538c80605d8a0fac9b40f48e97efa7de63" + integrity sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-transform-object-rest-spread@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.0.tgz#7b836ad0088fdded2420ce96d4e1d3ed78b71df1" @@ -812,6 +1365,16 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.23.3" +"@babel/plugin-transform-object-rest-spread@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz#d13a2b93435aeb8a197e115221cab266ba6e55d6" + integrity sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q== + dependencies: + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.24.7" + "@babel/plugin-transform-object-super@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz#81fdb636dcb306dd2e4e8fd80db5b2362ed2ebcd" @@ -820,6 +1383,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.20" +"@babel/plugin-transform-object-super@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz#66eeaff7830bba945dd8989b632a40c04ed625be" + integrity sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-replace-supers" "^7.24.7" + "@babel/plugin-transform-optional-catch-binding@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz#318066de6dacce7d92fa244ae475aa8d91778017" @@ -828,6 +1399,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" +"@babel/plugin-transform-optional-catch-binding@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz#00eabd883d0dd6a60c1c557548785919b6e717b4" + integrity sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-transform-optional-chaining@^7.23.3", "@babel/plugin-transform-optional-chaining@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz#6acf61203bdfc4de9d4e52e64490aeb3e52bd017" @@ -837,6 +1416,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" +"@babel/plugin-transform-optional-chaining@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz#b8f6848a80cf2da98a8a204429bec04756c6d454" + integrity sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-transform-parameters@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz#83ef5d1baf4b1072fa6e54b2b0999a7b2527e2af" @@ -844,6 +1432,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-parameters@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz#5881f0ae21018400e320fc7eb817e529d1254b68" + integrity sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-private-methods@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz#b2d7a3c97e278bfe59137a978d53b2c2e038c0e4" @@ -852,6 +1447,14 @@ "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-private-methods@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz#e6318746b2ae70a59d023d5cc1344a2ba7a75f5e" + integrity sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-private-property-in-object@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz#3ec711d05d6608fd173d9b8de39872d8dbf68bf5" @@ -862,6 +1465,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" +"@babel/plugin-transform-private-property-in-object@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz#4eec6bc701288c1fab5f72e6a4bbc9d67faca061" + integrity sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-transform-property-literals@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz#54518f14ac4755d22b92162e4a852d308a560875" @@ -869,6 +1482,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-property-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz#f0d2ed8380dfbed949c42d4d790266525d63bbdc" + integrity sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-react-constant-elements@^7.18.12": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.23.3.tgz#5efc001d07ef0f7da0d73c3a86c132f73d28e43c" @@ -876,6 +1496,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-react-constant-elements@^7.21.3": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.7.tgz#b85e8f240b14400277f106c9c9b585d9acf608a1" + integrity sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-react-display-name@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz#70529f034dd1e561045ad3c8152a267f0d7b6200" @@ -917,6 +1544,14 @@ "@babel/helper-plugin-utils" "^7.22.5" regenerator-transform "^0.15.2" +"@babel/plugin-transform-regenerator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz#021562de4534d8b4b1851759fd7af4e05d2c47f8" + integrity sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + regenerator-transform "^0.15.2" + "@babel/plugin-transform-reserved-words@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz#4130dcee12bd3dd5705c587947eb715da12efac8" @@ -924,6 +1559,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-reserved-words@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz#80037fe4fbf031fc1125022178ff3938bb3743a4" + integrity sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-runtime@^7.22.9": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz#e308fe27d08b74027d42547081eefaf4f2ffbcc9" @@ -943,6 +1585,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-shorthand-properties@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz#85448c6b996e122fa9e289746140aaa99da64e73" + integrity sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-spread@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz#41d17aacb12bde55168403c6f2d6bdca563d362c" @@ -951,6 +1600,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" +"@babel/plugin-transform-spread@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz#e8a38c0fde7882e0fb8f160378f74bd885cc7bb3" + integrity sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-transform-sticky-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz#dec45588ab4a723cb579c609b294a3d1bd22ff04" @@ -958,6 +1615,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-sticky-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz#96ae80d7a7e5251f657b5cf18f1ea6bf926f5feb" + integrity sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-template-literals@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz#5f0f028eb14e50b5d0f76be57f90045757539d07" @@ -965,6 +1629,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-template-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz#a05debb4a9072ae8f985bcf77f3f215434c8f8c8" + integrity sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-typeof-symbol@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz#9dfab97acc87495c0c449014eb9c547d8966bca4" @@ -972,6 +1643,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-typeof-symbol@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz#f074be466580d47d6e6b27473a840c9f9ca08fb0" + integrity sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-typescript@^7.23.3": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz#aa36a94e5da8d94339ae3a4e22d40ed287feb34c" @@ -982,6 +1660,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.23.3" +"@babel/plugin-transform-typescript@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.7.tgz#b006b3e0094bf0813d505e0c5485679eeaf4a881" + integrity sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-typescript" "^7.24.7" + "@babel/plugin-transform-unicode-escapes@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz#1f66d16cab01fab98d784867d24f70c1ca65b925" @@ -989,6 +1677,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-escapes@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz#2023a82ced1fb4971630a2e079764502c4148e0e" + integrity sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-unicode-property-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz#19e234129e5ffa7205010feec0d94c251083d7ad" @@ -997,6 +1692,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-property-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz#9073a4cd13b86ea71c3264659590ac086605bbcd" + integrity sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-unicode-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz#26897708d8f42654ca4ce1b73e96140fbad879dc" @@ -1005,6 +1708,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz#dfc3d4a51127108099b19817c0963be6a2adf19f" + integrity sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-transform-unicode-sets-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz#4fb6f0a719c2c5859d11f6b55a050cc987f3799e" @@ -1013,6 +1724,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-sets-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz#d40705d67523803a576e29c63cef6e516b858ed9" + integrity sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/preset-env@^7.19.4", "@babel/preset-env@^7.22.9": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.0.tgz#11536a7f4b977294f0bdfad780f01a8ac8e183fc" @@ -1099,6 +1818,93 @@ core-js-compat "^3.31.0" semver "^6.3.1" +"@babel/preset-env@^7.20.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.7.tgz#ff067b4e30ba4a72f225f12f123173e77b987f37" + integrity sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ== + dependencies: + "@babel/compat-data" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.7" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.7" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.24.7" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.24.7" + "@babel/plugin-transform-async-generator-functions" "^7.24.7" + "@babel/plugin-transform-async-to-generator" "^7.24.7" + "@babel/plugin-transform-block-scoped-functions" "^7.24.7" + "@babel/plugin-transform-block-scoping" "^7.24.7" + "@babel/plugin-transform-class-properties" "^7.24.7" + "@babel/plugin-transform-class-static-block" "^7.24.7" + "@babel/plugin-transform-classes" "^7.24.7" + "@babel/plugin-transform-computed-properties" "^7.24.7" + "@babel/plugin-transform-destructuring" "^7.24.7" + "@babel/plugin-transform-dotall-regex" "^7.24.7" + "@babel/plugin-transform-duplicate-keys" "^7.24.7" + "@babel/plugin-transform-dynamic-import" "^7.24.7" + "@babel/plugin-transform-exponentiation-operator" "^7.24.7" + "@babel/plugin-transform-export-namespace-from" "^7.24.7" + "@babel/plugin-transform-for-of" "^7.24.7" + "@babel/plugin-transform-function-name" "^7.24.7" + "@babel/plugin-transform-json-strings" "^7.24.7" + "@babel/plugin-transform-literals" "^7.24.7" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" + "@babel/plugin-transform-member-expression-literals" "^7.24.7" + "@babel/plugin-transform-modules-amd" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.7" + "@babel/plugin-transform-modules-systemjs" "^7.24.7" + "@babel/plugin-transform-modules-umd" "^7.24.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" + "@babel/plugin-transform-new-target" "^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" + "@babel/plugin-transform-numeric-separator" "^7.24.7" + "@babel/plugin-transform-object-rest-spread" "^7.24.7" + "@babel/plugin-transform-object-super" "^7.24.7" + "@babel/plugin-transform-optional-catch-binding" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.7" + "@babel/plugin-transform-parameters" "^7.24.7" + "@babel/plugin-transform-private-methods" "^7.24.7" + "@babel/plugin-transform-private-property-in-object" "^7.24.7" + "@babel/plugin-transform-property-literals" "^7.24.7" + "@babel/plugin-transform-regenerator" "^7.24.7" + "@babel/plugin-transform-reserved-words" "^7.24.7" + "@babel/plugin-transform-shorthand-properties" "^7.24.7" + "@babel/plugin-transform-spread" "^7.24.7" + "@babel/plugin-transform-sticky-regex" "^7.24.7" + "@babel/plugin-transform-template-literals" "^7.24.7" + "@babel/plugin-transform-typeof-symbol" "^7.24.7" + "@babel/plugin-transform-unicode-escapes" "^7.24.7" + "@babel/plugin-transform-unicode-property-regex" "^7.24.7" + "@babel/plugin-transform-unicode-regex" "^7.24.7" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.7" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.4" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.31.0" + semver "^6.3.1" + "@babel/preset-modules@0.1.6-no-external-plugins": version "0.1.6-no-external-plugins" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" @@ -1131,6 +1937,17 @@ "@babel/plugin-transform-modules-commonjs" "^7.23.3" "@babel/plugin-transform-typescript" "^7.23.3" +"@babel/preset-typescript@^7.21.0": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz#66cd86ea8f8c014855671d5ea9a737139cbbfef1" + integrity sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + "@babel/plugin-syntax-jsx" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.7" + "@babel/plugin-transform-typescript" "^7.24.7" + "@babel/regjsgen@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" @@ -1160,6 +1977,15 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/template@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" + integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/traverse@^7.22.8", "@babel/traverse@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.0.tgz#4a408fbf364ff73135c714a2ab46a5eab2831b1e" @@ -1176,6 +2002,22 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" + integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.20.0", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.6", "@babel/types@^7.24.0", "@babel/types@^7.4.4": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" @@ -1185,6 +2027,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.21.3", "@babel/types@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" + integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== + dependencies: + "@babel/helper-string-parser" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + "@braintree/sanitize-url@^6.0.1": version "6.0.4" resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" @@ -1290,10 +2141,10 @@ webpack-merge "^5.9.0" webpackbar "^5.0.2" -"@docusaurus/core@3.2.0", "@docusaurus/core@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.2.0.tgz#10acb993fb76960890d1aa43025245aaa8dcdbbb" - integrity sha512-WTO6vW4404nhTmK9NL+95nd13I1JveFwZ8iOBYxb4xt+N2S3KzY+mm+1YtWw2vV37FbYfH+w+KrlrRaWuy5Hzw== +"@docusaurus/core@3.4.0", "@docusaurus/core@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.4.0.tgz#bdbf1af4b2f25d1bf4a5b62ec6137d84c821cb3c" + integrity sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w== dependencies: "@babel/core" "^7.23.3" "@babel/generator" "^7.23.3" @@ -1305,14 +2156,12 @@ "@babel/runtime" "^7.22.6" "@babel/runtime-corejs3" "^7.22.6" "@babel/traverse" "^7.22.8" - "@docusaurus/cssnano-preset" "3.2.0" - "@docusaurus/logger" "3.2.0" - "@docusaurus/mdx-loader" "3.2.0" - "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-common" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" - "@svgr/webpack" "^6.5.1" + "@docusaurus/cssnano-preset" "3.4.0" + "@docusaurus/logger" "3.4.0" + "@docusaurus/mdx-loader" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-common" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" autoprefixer "^10.4.14" babel-loader "^9.1.3" babel-plugin-dynamic-import-node "^2.3.3" @@ -1326,8 +2175,8 @@ copy-webpack-plugin "^11.0.0" core-js "^3.31.1" css-loader "^6.8.1" - css-minimizer-webpack-plugin "^4.2.2" - cssnano "^5.1.15" + css-minimizer-webpack-plugin "^5.0.1" + cssnano "^6.1.2" del "^6.1.1" detect-port "^1.5.1" escape-html "^1.0.3" @@ -1347,7 +2196,7 @@ prompts "^2.4.2" react-dev-utils "^12.0.1" react-helmet-async "^1.3.0" - react-loadable "npm:@docusaurus/react-loadable@5.5.2" + react-loadable "npm:@docusaurus/react-loadable@6.0.0" react-loadable-ssr-addon-v5-slorber "^1.0.1" react-router "^5.3.4" react-router-config "^5.1.1" @@ -1376,14 +2225,14 @@ postcss-sort-media-queries "^4.4.1" tslib "^2.6.0" -"@docusaurus/cssnano-preset@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.2.0.tgz#0e0fbf19873a726f92e670b9d511e9f2828d6097" - integrity sha512-H88RXGUia7r/VF3XfyoA4kbwgpUZcKsObF6VvwBOP91EdArTf6lnHbJ/x8Ca79KS/zf98qaWyBGzW+5ez58Iyw== +"@docusaurus/cssnano-preset@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz#dc7922b3bbeabcefc9b60d0161680d81cf72c368" + integrity sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ== dependencies: - cssnano-preset-advanced "^5.3.10" - postcss "^8.4.26" - postcss-sort-media-queries "^4.4.1" + cssnano-preset-advanced "^6.1.2" + postcss "^8.4.38" + postcss-sort-media-queries "^5.2.0" tslib "^2.6.0" "@docusaurus/logger@3.1.1": @@ -1394,10 +2243,10 @@ chalk "^4.1.2" tslib "^2.6.0" -"@docusaurus/logger@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.2.0.tgz#99d2b09478bcba69c964ec0c8600d855fb8e9e0f" - integrity sha512-Z1R1NcOGXZ8CkIJSvjvyxnuDDSlx/+1xlh20iVTw1DZRjonFmI3T3tTgk40YpXyWUYQpIgAoqqPMpuseMMdgRQ== +"@docusaurus/logger@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.4.0.tgz#8b0ac05c7f3dac2009066e2f964dee8209a77403" + integrity sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q== dependencies: chalk "^4.1.2" tslib "^2.6.0" @@ -1434,14 +2283,14 @@ vfile "^6.0.1" webpack "^5.88.1" -"@docusaurus/mdx-loader@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.2.0.tgz#d17f17ae1bb38255643c82705dda719b23c27831" - integrity sha512-JtkI5o6R/rJSr1Y23cHKz085aBJCvJw3AYHihJ7r+mBX+O8EuQIynG0e6/XpbSCpr7Ino0U50UtxaXcEbFwg9Q== +"@docusaurus/mdx-loader@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz#483d7ab57928fdbb5c8bd1678098721a930fc5f6" + integrity sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw== dependencies: - "@docusaurus/logger" "3.2.0" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" + "@docusaurus/logger" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" "@mdx-js/mdx" "^3.0.0" "@slorber/remark-comment" "^1.0.0" escape-html "^1.0.3" @@ -1478,32 +2327,46 @@ react-helmet-async "*" react-loadable "npm:@docusaurus/react-loadable@5.5.2" -"@docusaurus/module-type-aliases@3.2.0", "@docusaurus/module-type-aliases@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.2.0.tgz#ef883d8418f37e551eca72adc409014e720786d4" - integrity sha512-jRSp9YkvBwwNz6Xgy0RJPsnie+Ebb//gy7GdbkJ2pW2gvvlYKGib2+jSF0pfIzvyZLulfCynS1KQdvDKdSl8zQ== +"@docusaurus/module-type-aliases@3.4.0", "@docusaurus/module-type-aliases@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz#2653bde58fc1aa3dbc626a6c08cfb63a37ae1bb8" + integrity sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw== dependencies: - "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/types" "3.2.0" + "@docusaurus/types" "3.4.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" "@types/react-router-dom" "*" react-helmet-async "*" - react-loadable "npm:@docusaurus/react-loadable@5.5.2" + react-loadable "npm:@docusaurus/react-loadable@6.0.0" + +"@docusaurus/plugin-client-redirects@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.4.0.tgz#10eafc9adcf3f9be7cc33d77e816040dc7a8d368" + integrity sha512-Pr8kyh/+OsmYCvdZhc60jy/FnrY6flD2TEAhl4rJxeVFxnvvRgEhoaIVX8q9MuJmaQoh6frPk94pjs7/6YgBDQ== + dependencies: + "@docusaurus/core" "3.4.0" + "@docusaurus/logger" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-common" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" + eta "^2.2.0" + fs-extra "^11.1.1" + lodash "^4.17.21" + tslib "^2.6.0" -"@docusaurus/plugin-content-blog@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.2.0.tgz#b7b43e71634272a80a9532dc166731332391cb4b" - integrity sha512-MABqwjSicyHmYEfQueMthPCz18JkVxhK3EGhXTSRWwReAZ0UTuw9pG6+Wo+uXAugDaIcJH28rVZSwTDINPm2bw== - dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/logger" "3.2.0" - "@docusaurus/mdx-loader" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-common" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" +"@docusaurus/plugin-content-blog@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz#6373632fdbababbda73a13c4a08f907d7de8f007" + integrity sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw== + dependencies: + "@docusaurus/core" "3.4.0" + "@docusaurus/logger" "3.4.0" + "@docusaurus/mdx-loader" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-common" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" cheerio "^1.0.0-rc.12" feed "^4.2.2" fs-extra "^11.1.1" @@ -1515,19 +2378,19 @@ utility-types "^3.10.0" webpack "^5.88.1" -"@docusaurus/plugin-content-docs@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.2.0.tgz#6e4f727a0cce301b9d9361bf41ca6a978fe79475" - integrity sha512-uuqhahmsBnirxOz+SXksnWt7+wc+iN4ntxNRH48BUgo7QRNLATWjHCgI8t6zrMJxK4o+QL9DhLaPDlFHs91B3Q== - dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/logger" "3.2.0" - "@docusaurus/mdx-loader" "3.2.0" - "@docusaurus/module-type-aliases" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-common" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" +"@docusaurus/plugin-content-docs@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz#3088973f72169a2a6d533afccec7153c8720d332" + integrity sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg== + dependencies: + "@docusaurus/core" "3.4.0" + "@docusaurus/logger" "3.4.0" + "@docusaurus/mdx-loader" "3.4.0" + "@docusaurus/module-type-aliases" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-common" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" "@types/react-router-config" "^5.0.7" combine-promises "^1.1.0" fs-extra "^11.1.1" @@ -1558,98 +2421,98 @@ utility-types "^3.10.0" webpack "^5.88.1" -"@docusaurus/plugin-content-pages@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.2.0.tgz#df28a6ee6b52c4b292a726f29f39b119756caf44" - integrity sha512-4ofAN7JDsdb4tODO9OIrizWY5DmEJXr0eu+UDIkLqGP+gXXTahJZv8h2mlxO+lPXGXRCVBOfA14OG1hOYJVPwA== - dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/mdx-loader" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" +"@docusaurus/plugin-content-pages@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz#1846172ca0355c7d32a67ef8377750ce02bbb8ad" + integrity sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg== + dependencies: + "@docusaurus/core" "3.4.0" + "@docusaurus/mdx-loader" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" fs-extra "^11.1.1" tslib "^2.6.0" webpack "^5.88.1" -"@docusaurus/plugin-debug@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.2.0.tgz#643d13d403685c2b9bdb3b65bec8050847b920a3" - integrity sha512-p6WxtO5XZGz66y6QNQtCJwBefq4S6/w75XaXVvH1/2P9uaijvF7R+Cm2EWQZ5WsvA5wl//DFWblyDHRyVC207Q== +"@docusaurus/plugin-debug@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz#74e4ec5686fa314c26f3ac150bacadbba7f06948" + integrity sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg== dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils" "3.2.0" + "@docusaurus/core" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils" "3.4.0" fs-extra "^11.1.1" react-json-view-lite "^1.2.0" tslib "^2.6.0" -"@docusaurus/plugin-google-analytics@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.2.0.tgz#770151947c0ee49500586e9200631852ab97e23a" - integrity sha512-//TepJTEyAZSvBwHKEbXHu9xT/VkK3wUil2ZakKvQZYfUC01uWn6A1E3toa8R7WhCy1xPUeIukqmJy1Clg8njQ== +"@docusaurus/plugin-google-analytics@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz#5f59fc25329a59decc231936f6f9fb5663da3c55" + integrity sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA== dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" + "@docusaurus/core" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" tslib "^2.6.0" -"@docusaurus/plugin-google-gtag@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.2.0.tgz#65fc7ddc242185c3a10e60308471564075229406" - integrity sha512-3s6zxlaMMb87MW2Rxy6EnSRDs0WDEQPuHilZZH402C8kOrUnIwlhlfjWZ4ZyLDziGl/Eec/DvD0PVqj0qHRomA== +"@docusaurus/plugin-google-gtag@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz#42489ac5fe1c83b5523ceedd5ef74f9aa8bc251b" + integrity sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA== dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" + "@docusaurus/core" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" "@types/gtag.js" "^0.0.12" tslib "^2.6.0" -"@docusaurus/plugin-google-tag-manager@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.2.0.tgz#730c28a43ff5073f595509c6cb77ce4311a2e369" - integrity sha512-rAKtsJ11vPHA7dTAqWCgyIy7AyFRF/lpI77Zd/4HKgqcIvIayVBvL3QtelhUazfYTLTH6ls6kQ9wjMcIFxRiGg== +"@docusaurus/plugin-google-tag-manager@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz#cebb03a5ffa1e70b37d95601442babea251329ff" + integrity sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ== dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" + "@docusaurus/core" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" tslib "^2.6.0" -"@docusaurus/plugin-sitemap@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.2.0.tgz#cae7c92a8631072fff39dd5caf5ea7608c795540" - integrity sha512-gnWDFt6MStjLkdtt63Lzc+14EPSd8B6mzJGJp9GQMvWDUoMAUijUqpVIHYQq+DPMcI4PJZ5I2nsl5XFf1vOldA== - dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/logger" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-common" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" +"@docusaurus/plugin-sitemap@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz#b091d64d1e3c6c872050189999580187537bcbc6" + integrity sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q== + dependencies: + "@docusaurus/core" "3.4.0" + "@docusaurus/logger" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-common" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" fs-extra "^11.1.1" sitemap "^7.1.1" tslib "^2.6.0" -"@docusaurus/preset-classic@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.2.0.tgz#f64d970eace76c61e4f1b4b7d85d9f69d4a2dd0e" - integrity sha512-t7tXyk8kUgT7hUqEOgSJnPs+Foem9ucuan/a9QVYaVFCDjp92Sb2FpCY8bVasAokYCjodYe2LfpAoSCj5YDYWg== - dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/plugin-content-blog" "3.2.0" - "@docusaurus/plugin-content-docs" "3.2.0" - "@docusaurus/plugin-content-pages" "3.2.0" - "@docusaurus/plugin-debug" "3.2.0" - "@docusaurus/plugin-google-analytics" "3.2.0" - "@docusaurus/plugin-google-gtag" "3.2.0" - "@docusaurus/plugin-google-tag-manager" "3.2.0" - "@docusaurus/plugin-sitemap" "3.2.0" - "@docusaurus/theme-classic" "3.2.0" - "@docusaurus/theme-common" "3.2.0" - "@docusaurus/theme-search-algolia" "3.2.0" - "@docusaurus/types" "3.2.0" - -"@docusaurus/react-loadable@5.5.2": +"@docusaurus/preset-classic@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz#6082a32fbb465b0cb2c2a50ebfc277cff2c0f139" + integrity sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg== + dependencies: + "@docusaurus/core" "3.4.0" + "@docusaurus/plugin-content-blog" "3.4.0" + "@docusaurus/plugin-content-docs" "3.4.0" + "@docusaurus/plugin-content-pages" "3.4.0" + "@docusaurus/plugin-debug" "3.4.0" + "@docusaurus/plugin-google-analytics" "3.4.0" + "@docusaurus/plugin-google-gtag" "3.4.0" + "@docusaurus/plugin-google-tag-manager" "3.4.0" + "@docusaurus/plugin-sitemap" "3.4.0" + "@docusaurus/theme-classic" "3.4.0" + "@docusaurus/theme-common" "3.4.0" + "@docusaurus/theme-search-algolia" "3.4.0" + "@docusaurus/types" "3.4.0" + +"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": version "5.5.2" resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== @@ -1657,23 +2520,23 @@ "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/theme-classic@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.2.0.tgz#4aa229f1a4b1b4c138a5c80089f1d8146f56252c" - integrity sha512-4oSO5BQOJ5ja7WYdL6jK1n4J96tp+VJHamdwao6Ea252sA3W3vvR0otTflG4p4XVjNZH6hlPQoi5lKW0HeRgfQ== - dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/mdx-loader" "3.2.0" - "@docusaurus/module-type-aliases" "3.2.0" - "@docusaurus/plugin-content-blog" "3.2.0" - "@docusaurus/plugin-content-docs" "3.2.0" - "@docusaurus/plugin-content-pages" "3.2.0" - "@docusaurus/theme-common" "3.2.0" - "@docusaurus/theme-translations" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-common" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" +"@docusaurus/theme-classic@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz#1b0f48edec3e3ec8927843554b9f11e5927b0e52" + integrity sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q== + dependencies: + "@docusaurus/core" "3.4.0" + "@docusaurus/mdx-loader" "3.4.0" + "@docusaurus/module-type-aliases" "3.4.0" + "@docusaurus/plugin-content-blog" "3.4.0" + "@docusaurus/plugin-content-docs" "3.4.0" + "@docusaurus/plugin-content-pages" "3.4.0" + "@docusaurus/theme-common" "3.4.0" + "@docusaurus/theme-translations" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-common" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" "@mdx-js/react" "^3.0.0" clsx "^2.0.0" copy-text-to-clipboard "^3.2.0" @@ -1688,18 +2551,18 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-common@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.2.0.tgz#67f5f1a1e265e1f1a5b9fa7bfb4bf7b98dfcf981" - integrity sha512-sFbw9XviNJJ+760kAcZCQMQ3jkNIznGqa6MQ70E5BnbP+ja36kGgPOfjcsvAcNey1H1Rkhh3p2Mhf4HVLdKVVw== - dependencies: - "@docusaurus/mdx-loader" "3.2.0" - "@docusaurus/module-type-aliases" "3.2.0" - "@docusaurus/plugin-content-blog" "3.2.0" - "@docusaurus/plugin-content-docs" "3.2.0" - "@docusaurus/plugin-content-pages" "3.2.0" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-common" "3.2.0" +"@docusaurus/theme-common@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.4.0.tgz#01f2b728de6cb57f6443f52fc30675cf12a5d49f" + integrity sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA== + dependencies: + "@docusaurus/mdx-loader" "3.4.0" + "@docusaurus/module-type-aliases" "3.4.0" + "@docusaurus/plugin-content-blog" "3.4.0" + "@docusaurus/plugin-content-docs" "3.4.0" + "@docusaurus/plugin-content-pages" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-common" "3.4.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" @@ -1709,32 +2572,32 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-mermaid@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.2.0.tgz#b6d43b853ccc562a9c0be28e1100dad6324b8a8f" - integrity sha512-PvN6K6m3JaM9cr9oSPyba6OlwAiSfBzqQtNqdgPFDjakKuT4kj6JODfExi+HKtWuxayOVRQlRl7zTnWxM4sTVw== - dependencies: - "@docusaurus/core" "3.2.0" - "@docusaurus/module-type-aliases" "3.2.0" - "@docusaurus/theme-common" "3.2.0" - "@docusaurus/types" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" +"@docusaurus/theme-mermaid@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.4.0.tgz#ef1d2231d0858767f67538b4fafd7d0ce2a3e845" + integrity sha512-3w5QW0HEZ2O6x2w6lU3ZvOe1gNXP2HIoKDMJBil1VmLBc9PmpAG17VmfhI/p3L2etNmOiVs5GgniUqvn8AFEGQ== + dependencies: + "@docusaurus/core" "3.4.0" + "@docusaurus/module-type-aliases" "3.4.0" + "@docusaurus/theme-common" "3.4.0" + "@docusaurus/types" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" mermaid "^10.4.0" tslib "^2.6.0" -"@docusaurus/theme-search-algolia@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.2.0.tgz#05338b37753dd13899fb0296f2c57130e9893dbf" - integrity sha512-PgvF4qHoqJp8+GfqClUbTF/zYNOsz4De251IuzXon7+7FAXwvb2qmYtA2nEwyMbB7faKOz33Pxzv+y+153KS/g== +"@docusaurus/theme-search-algolia@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz#c499bad71d668df0d0f15b0e5e33e2fc4e330fcc" + integrity sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q== dependencies: "@docsearch/react" "^3.5.2" - "@docusaurus/core" "3.2.0" - "@docusaurus/logger" "3.2.0" - "@docusaurus/plugin-content-docs" "3.2.0" - "@docusaurus/theme-common" "3.2.0" - "@docusaurus/theme-translations" "3.2.0" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-validation" "3.2.0" + "@docusaurus/core" "3.4.0" + "@docusaurus/logger" "3.4.0" + "@docusaurus/plugin-content-docs" "3.4.0" + "@docusaurus/theme-common" "3.4.0" + "@docusaurus/theme-translations" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-validation" "3.4.0" algoliasearch "^4.18.0" algoliasearch-helper "^3.13.3" clsx "^2.0.0" @@ -1744,10 +2607,10 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-translations@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.2.0.tgz#02a0e9bd0ed8cebc21a2f1e5b6d252b0e5ee39a9" - integrity sha512-VXzZJBuyVEmwUYyud+7IgJQEBRM6R2u/s10Rp3DOP19CBQxeKgHYTKkKhFtDeKMHDassb665kjgOi0YlJfUT6w== +"@docusaurus/theme-translations@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz#e6355d01352886c67e38e848b2542582ea3070af" + integrity sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg== dependencies: fs-extra "^11.1.1" tslib "^2.6.0" @@ -1775,10 +2638,10 @@ webpack "^5.88.1" webpack-merge "^5.9.0" -"@docusaurus/types@3.2.0", "@docusaurus/types@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.2.0.tgz#c5bfd000ad4f72e9a7e6beff79905f9ea165fcd3" - integrity sha512-uG3FfTkkkbZIPPNYx6xRfZHKeGyRd/inIT1cqvYt1FobFLd+7WhRXrSBqwJ9JajJjEAjNioRMVFgGofGf/Wdww== +"@docusaurus/types@3.4.0", "@docusaurus/types@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.4.0.tgz#237c3f737e9db3f7c1a5935a3ef48d6eadde8292" + integrity sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A== dependencies: "@mdx-js/mdx" "^3.0.0" "@types/history" "^4.7.11" @@ -1797,10 +2660,10 @@ dependencies: tslib "^2.6.0" -"@docusaurus/utils-common@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.2.0.tgz#6163a2c415d150d6df73a8aceec6004f0ba3bb06" - integrity sha512-WEQT5L2lT/tBQgDRgeZQAIi9YJBrwEILb1BuObQn1St3T/4K1gx5fWwOT8qdLOov296XLd1FQg9Ywu27aE9svw== +"@docusaurus/utils-common@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.4.0.tgz#2a43fefd35b85ab9fcc6833187e66c15f8bfbbc6" + integrity sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ== dependencies: tslib "^2.6.0" @@ -1815,16 +2678,18 @@ js-yaml "^4.1.0" tslib "^2.6.0" -"@docusaurus/utils-validation@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.2.0.tgz#b53463d9dc6eb335a2ad93ed4b3c397162533e6d" - integrity sha512-rCzMTqwNrBrEOyU8EaD1fYWdig4TDhfj+YLqB8DY68VUAqSIgbY+yshpqFKB0bznFYNBJbn0bGpvVuImQOa/vA== +"@docusaurus/utils-validation@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz#0176f6e503ff45f4390ec2ecb69550f55e0b5eb7" + integrity sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g== dependencies: - "@docusaurus/logger" "3.2.0" - "@docusaurus/utils" "3.2.0" - "@docusaurus/utils-common" "3.2.0" + "@docusaurus/logger" "3.4.0" + "@docusaurus/utils" "3.4.0" + "@docusaurus/utils-common" "3.4.0" + fs-extra "^11.2.0" joi "^17.9.2" js-yaml "^4.1.0" + lodash "^4.17.21" tslib "^2.6.0" "@docusaurus/utils@3.1.1", "@docusaurus/utils@^2 || ^3": @@ -1850,14 +2715,14 @@ url-loader "^4.1.1" webpack "^5.88.1" -"@docusaurus/utils@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.2.0.tgz#1312221d224eb2cbaaaf53d24efca3e1e976db3e" - integrity sha512-3rgrE7iL60yV2JQivlcoxUNNTK2APmn+OHLUmTvX2pueIM8DEOCEFHpJO4MiWjFO7V/Wq3iA/W1M03JnjdugVw== +"@docusaurus/utils@3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.4.0.tgz#c508e20627b7a55e2b541e4a28c95e0637d6a204" + integrity sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g== dependencies: - "@docusaurus/logger" "3.2.0" - "@docusaurus/utils-common" "3.2.0" - "@svgr/webpack" "^6.5.1" + "@docusaurus/logger" "3.4.0" + "@docusaurus/utils-common" "3.4.0" + "@svgr/webpack" "^8.1.0" escape-string-regexp "^4.0.0" file-loader "^6.2.0" fs-extra "^11.1.1" @@ -1873,6 +2738,7 @@ shelljs "^0.8.5" tslib "^2.6.0" url-loader "^4.1.1" + utility-types "^3.10.0" webpack "^5.88.1" "@easyops-cn/autocomplete.js@^0.38.1": @@ -1982,7 +2848,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.9": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -2229,46 +3095,90 @@ p-map "^4.0.0" webpack-sources "^3.2.2" +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + "@svgr/babel-plugin-add-jsx-attribute@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba" integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== -"@svgr/babel-plugin-remove-jsx-attribute@*": +"@svgr/babel-plugin-remove-jsx-attribute@*", "@svgr/babel-plugin-remove-jsx-attribute@8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== -"@svgr/babel-plugin-remove-jsx-empty-expression@*": +"@svgr/babel-plugin-remove-jsx-empty-expression@*", "@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + "@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60" integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + "@svgr/babel-plugin-svg-dynamic-title@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4" integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + "@svgr/babel-plugin-svg-em-dimensions@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217" integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + "@svgr/babel-plugin-transform-react-native-svg@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305" integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + "@svgr/babel-plugin-transform-svg-component@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250" integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + "@svgr/babel-preset@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828" @@ -2283,6 +3193,17 @@ "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" "@svgr/babel-plugin-transform-svg-component" "^6.5.1" +"@svgr/core@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + "@svgr/core@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.5.1.tgz#d3e8aa9dbe3fbd747f9ee4282c1c77a27410488a" @@ -2294,6 +3215,14 @@ camelcase "^6.2.0" cosmiconfig "^7.0.1" +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + "@svgr/hast-util-to-babel-ast@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2" @@ -2302,6 +3231,16 @@ "@babel/types" "^7.20.0" entities "^4.4.0" +"@svgr/plugin-jsx@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + "@svgr/plugin-jsx@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072" @@ -2312,6 +3251,15 @@ "@svgr/hast-util-to-babel-ast" "^6.5.1" svg-parser "^2.0.4" +"@svgr/plugin-svgo@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz#b115b7b967b564f89ac58feae89b88c3decd0f00" + integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA== + dependencies: + cosmiconfig "^8.1.3" + deepmerge "^4.3.1" + svgo "^3.0.2" + "@svgr/plugin-svgo@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84" @@ -2335,6 +3283,20 @@ "@svgr/plugin-jsx" "^6.5.1" "@svgr/plugin-svgo" "^6.5.1" +"@svgr/webpack@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-8.1.0.tgz#16f1b5346f102f89fda6ec7338b96a701d8be0c2" + integrity sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA== + dependencies: + "@babel/core" "^7.21.3" + "@babel/plugin-transform-react-constant-elements" "^7.21.3" + "@babel/preset-env" "^7.20.2" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.21.0" + "@svgr/core" "8.1.0" + "@svgr/plugin-jsx" "8.1.0" + "@svgr/plugin-svgo" "8.1.0" + "@szmarczak/http-timer@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" @@ -3057,6 +4019,18 @@ autoprefixer@^10.4.12, autoprefixer@^10.4.14: picocolors "^1.0.0" postcss-value-parser "^4.2.0" +autoprefixer@^10.4.19: + version "10.4.19" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" + integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== + dependencies: + browserslist "^4.23.0" + caniuse-lite "^1.0.30001599" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + babel-loader@^9.1.3: version "9.1.3" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.1.3.tgz#3d0e01b4e69760cc694ee306fe16d358aa1c6f9a" @@ -3072,6 +4046,15 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.11" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" + integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.2" + semver "^6.3.1" + babel-plugin-polyfill-corejs2@^0.4.8: version "0.4.8" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz#dbcc3c8ca758a290d47c3c6a490d59429b0d2269" @@ -3081,6 +4064,14 @@ babel-plugin-polyfill-corejs2@^0.4.8: "@babel/helper-define-polyfill-provider" "^0.5.0" semver "^6.3.1" +babel-plugin-polyfill-corejs3@^0.10.4: + version "0.10.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" + integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.1" + core-js-compat "^3.36.1" + babel-plugin-polyfill-corejs3@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz#9eea32349d94556c2ad3ab9b82ebb27d4bf04a81" @@ -3096,6 +4087,13 @@ babel-plugin-polyfill-regenerator@^0.5.5: dependencies: "@babel/helper-define-polyfill-provider" "^0.5.0" +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" + integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + bail@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" @@ -3287,6 +4285,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001591: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001594.tgz#bea552414cd52c2d0c985ed9206314a696e685f5" integrity sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g== +caniuse-lite@^1.0.30001599: + version "1.0.30001629" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001629.tgz#907a36f4669031bd8a1a8dbc2fa08b29e0db297e" + integrity sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw== + ccount@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" @@ -3463,7 +4466,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colord@^2.9.1: +colord@^2.9.1, colord@^2.9.3: version "2.9.3" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== @@ -3623,6 +4626,13 @@ core-js-compat@^3.31.0, core-js-compat@^3.34.0: dependencies: browserslist "^4.22.3" +core-js-compat@^3.36.1: + version "3.37.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee" + integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== + dependencies: + browserslist "^4.23.0" + core-js-pure@^3.30.2: version "3.36.0" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.36.0.tgz#ffb34330b14e594d6a9835cf5843b4123f1d95db" @@ -3667,7 +4677,7 @@ cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -cosmiconfig@^8.3.5: +cosmiconfig@^8.1.3, cosmiconfig@^8.3.5: version "8.3.6" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== @@ -3698,6 +4708,11 @@ css-declaration-sorter@^6.3.1: resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== +css-declaration-sorter@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz#6dec1c9523bc4a643e088aab8f09e67a54961024" + integrity sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow== + css-loader@^6.8.1: version "6.10.0" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.10.0.tgz#7c172b270ec7b833951b52c348861206b184a4b7" @@ -3724,6 +4739,18 @@ css-minimizer-webpack-plugin@^4.2.2: serialize-javascript "^6.0.0" source-map "^0.6.1" +css-minimizer-webpack-plugin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz#33effe662edb1a0bf08ad633c32fa75d0f7ec565" + integrity sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg== + dependencies: + "@jridgewell/trace-mapping" "^0.3.18" + cssnano "^6.0.1" + jest-worker "^29.4.3" + postcss "^8.4.24" + schema-utils "^4.0.1" + serialize-javascript "^6.0.1" + css-select@^4.1.3: version "4.3.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" @@ -3754,6 +4781,22 @@ css-tree@^1.1.2, css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" @@ -3776,6 +4819,19 @@ cssnano-preset-advanced@^5.3.10: postcss-reduce-idents "^5.2.0" postcss-zindex "^5.1.0" +cssnano-preset-advanced@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz#82b090872b8f98c471f681d541c735acf8b94d3f" + integrity sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ== + dependencies: + autoprefixer "^10.4.19" + browserslist "^4.23.0" + cssnano-preset-default "^6.1.2" + postcss-discard-unused "^6.0.5" + postcss-merge-idents "^6.0.3" + postcss-reduce-idents "^6.0.3" + postcss-zindex "^6.0.2" + cssnano-preset-default@^5.2.14: version "5.2.14" resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" @@ -3811,11 +4867,52 @@ cssnano-preset-default@^5.2.14: postcss-svgo "^5.1.0" postcss-unique-selectors "^5.1.1" +cssnano-preset-default@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz#adf4b89b975aa775f2750c89dbaf199bbd9da35e" + integrity sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg== + dependencies: + browserslist "^4.23.0" + css-declaration-sorter "^7.2.0" + cssnano-utils "^4.0.2" + postcss-calc "^9.0.1" + postcss-colormin "^6.1.0" + postcss-convert-values "^6.1.0" + postcss-discard-comments "^6.0.2" + postcss-discard-duplicates "^6.0.3" + postcss-discard-empty "^6.0.3" + postcss-discard-overridden "^6.0.2" + postcss-merge-longhand "^6.0.5" + postcss-merge-rules "^6.1.1" + postcss-minify-font-values "^6.1.0" + postcss-minify-gradients "^6.0.3" + postcss-minify-params "^6.1.0" + postcss-minify-selectors "^6.0.4" + postcss-normalize-charset "^6.0.2" + postcss-normalize-display-values "^6.0.2" + postcss-normalize-positions "^6.0.2" + postcss-normalize-repeat-style "^6.0.2" + postcss-normalize-string "^6.0.2" + postcss-normalize-timing-functions "^6.0.2" + postcss-normalize-unicode "^6.1.0" + postcss-normalize-url "^6.0.2" + postcss-normalize-whitespace "^6.0.2" + postcss-ordered-values "^6.0.2" + postcss-reduce-initial "^6.1.0" + postcss-reduce-transforms "^6.0.2" + postcss-svgo "^6.0.3" + postcss-unique-selectors "^6.0.4" + cssnano-utils@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== +cssnano-utils@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.2.tgz#56f61c126cd0f11f2eef1596239d730d9fceff3c" + integrity sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ== + cssnano@^5.1.15, cssnano@^5.1.8: version "5.1.15" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" @@ -3825,6 +4922,14 @@ cssnano@^5.1.15, cssnano@^5.1.8: lilconfig "^2.0.3" yaml "^1.10.2" +cssnano@^6.0.1, cssnano@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.1.2.tgz#4bd19e505bd37ee7cf0dc902d3d869f6d79c66b8" + integrity sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA== + dependencies: + cssnano-preset-default "^6.1.2" + lilconfig "^3.1.1" + csso@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" @@ -3832,6 +4937,13 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + csstype@^3.0.2: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" @@ -4174,7 +5286,7 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deepmerge@^4.2.2: +deepmerge@^4.2.2, deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -4875,7 +5987,7 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^11.1.1: +fs-extra@^11.1.1, fs-extra@^11.2.0: version "11.2.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== @@ -5776,7 +6888,7 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.1.2: +jest-worker@^29.1.2, jest-worker@^29.4.3: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== @@ -5937,6 +7049,11 @@ lilconfig@^2.0.3: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== +lilconfig@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" + integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -6318,6 +7435,16 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -7474,6 +8601,14 @@ postcss-calc@^8.2.3: postcss-selector-parser "^6.0.9" postcss-value-parser "^4.2.0" +postcss-calc@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" + integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ== + dependencies: + postcss-selector-parser "^6.0.11" + postcss-value-parser "^4.2.0" + postcss-colormin@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" @@ -7484,6 +8619,16 @@ postcss-colormin@^5.3.1: colord "^2.9.1" postcss-value-parser "^4.2.0" +postcss-colormin@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.1.0.tgz#076e8d3fb291fbff7b10e6b063be9da42ff6488d" + integrity sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw== + dependencies: + browserslist "^4.23.0" + caniuse-api "^3.0.0" + colord "^2.9.3" + postcss-value-parser "^4.2.0" + postcss-convert-values@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" @@ -7492,26 +8637,54 @@ postcss-convert-values@^5.1.3: browserslist "^4.21.4" postcss-value-parser "^4.2.0" +postcss-convert-values@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz#3498387f8efedb817cbc63901d45bd1ceaa40f48" + integrity sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w== + dependencies: + browserslist "^4.23.0" + postcss-value-parser "^4.2.0" + postcss-discard-comments@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== +postcss-discard-comments@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz#e768dcfdc33e0216380623652b0a4f69f4678b6c" + integrity sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw== + postcss-discard-duplicates@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== +postcss-discard-duplicates@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz#d121e893c38dc58a67277f75bb58ba43fce4c3eb" + integrity sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw== + postcss-discard-empty@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== +postcss-discard-empty@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz#ee39c327219bb70473a066f772621f81435a79d9" + integrity sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ== + postcss-discard-overridden@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== +postcss-discard-overridden@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz#4e9f9c62ecd2df46e8fdb44dc17e189776572e2d" + integrity sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ== + postcss-discard-unused@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142" @@ -7519,6 +8692,13 @@ postcss-discard-unused@^5.1.0: dependencies: postcss-selector-parser "^6.0.5" +postcss-discard-unused@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz#c1b0e8c032c6054c3fbd22aaddba5b248136f338" + integrity sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA== + dependencies: + postcss-selector-parser "^6.0.16" + postcss-loader@^7.3.3: version "7.3.4" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.4.tgz#aed9b79ce4ed7e9e89e56199d25ad1ec8f606209" @@ -7536,6 +8716,14 @@ postcss-merge-idents@^5.1.1: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" +postcss-merge-idents@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz#7b9c31c7bc823c94bec50f297f04e3c2b838ea65" + integrity sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g== + dependencies: + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + postcss-merge-longhand@^5.1.7: version "5.1.7" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" @@ -7544,6 +8732,14 @@ postcss-merge-longhand@^5.1.7: postcss-value-parser "^4.2.0" stylehacks "^5.1.1" +postcss-merge-longhand@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz#ba8a8d473617c34a36abbea8dda2b215750a065a" + integrity sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^6.1.1" + postcss-merge-rules@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" @@ -7554,6 +8750,16 @@ postcss-merge-rules@^5.1.4: cssnano-utils "^3.1.0" postcss-selector-parser "^6.0.5" +postcss-merge-rules@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz#7aa539dceddab56019469c0edd7d22b64c3dea9d" + integrity sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ== + dependencies: + browserslist "^4.23.0" + caniuse-api "^3.0.0" + cssnano-utils "^4.0.2" + postcss-selector-parser "^6.0.16" + postcss-minify-font-values@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" @@ -7561,6 +8767,13 @@ postcss-minify-font-values@^5.1.0: dependencies: postcss-value-parser "^4.2.0" +postcss-minify-font-values@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz#a0e574c02ee3f299be2846369211f3b957ea4c59" + integrity sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg== + dependencies: + postcss-value-parser "^4.2.0" + postcss-minify-gradients@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" @@ -7570,6 +8783,15 @@ postcss-minify-gradients@^5.1.1: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" +postcss-minify-gradients@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz#ca3eb55a7bdb48a1e187a55c6377be918743dbd6" + integrity sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q== + dependencies: + colord "^2.9.3" + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + postcss-minify-params@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" @@ -7579,6 +8801,15 @@ postcss-minify-params@^5.1.4: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" +postcss-minify-params@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz#54551dec77b9a45a29c3cb5953bf7325a399ba08" + integrity sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA== + dependencies: + browserslist "^4.23.0" + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + postcss-minify-selectors@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" @@ -7586,6 +8817,13 @@ postcss-minify-selectors@^5.2.1: dependencies: postcss-selector-parser "^6.0.5" +postcss-minify-selectors@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz#197f7d72e6dd19eed47916d575d69dc38b396aff" + integrity sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ== + dependencies: + postcss-selector-parser "^6.0.16" + postcss-modules-extract-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" @@ -7619,6 +8857,11 @@ postcss-normalize-charset@^5.1.0: resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== +postcss-normalize-charset@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz#1ec25c435057a8001dac942942a95ffe66f721e1" + integrity sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ== + postcss-normalize-display-values@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" @@ -7626,6 +8869,13 @@ postcss-normalize-display-values@^5.1.0: dependencies: postcss-value-parser "^4.2.0" +postcss-normalize-display-values@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz#54f02764fed0b288d5363cbb140d6950dbbdd535" + integrity sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg== + dependencies: + postcss-value-parser "^4.2.0" + postcss-normalize-positions@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" @@ -7633,6 +8883,13 @@ postcss-normalize-positions@^5.1.1: dependencies: postcss-value-parser "^4.2.0" +postcss-normalize-positions@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz#e982d284ec878b9b819796266f640852dbbb723a" + integrity sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q== + dependencies: + postcss-value-parser "^4.2.0" + postcss-normalize-repeat-style@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" @@ -7640,6 +8897,13 @@ postcss-normalize-repeat-style@^5.1.1: dependencies: postcss-value-parser "^4.2.0" +postcss-normalize-repeat-style@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz#f8006942fd0617c73f049dd8b6201c3a3040ecf3" + integrity sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ== + dependencies: + postcss-value-parser "^4.2.0" + postcss-normalize-string@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" @@ -7647,6 +8911,13 @@ postcss-normalize-string@^5.1.0: dependencies: postcss-value-parser "^4.2.0" +postcss-normalize-string@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz#e3cc6ad5c95581acd1fc8774b309dd7c06e5e363" + integrity sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ== + dependencies: + postcss-value-parser "^4.2.0" + postcss-normalize-timing-functions@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" @@ -7654,6 +8925,13 @@ postcss-normalize-timing-functions@^5.1.0: dependencies: postcss-value-parser "^4.2.0" +postcss-normalize-timing-functions@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz#40cb8726cef999de984527cbd9d1db1f3e9062c0" + integrity sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA== + dependencies: + postcss-value-parser "^4.2.0" + postcss-normalize-unicode@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" @@ -7662,6 +8940,14 @@ postcss-normalize-unicode@^5.1.1: browserslist "^4.21.4" postcss-value-parser "^4.2.0" +postcss-normalize-unicode@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz#aaf8bbd34c306e230777e80f7f12a4b7d27ce06e" + integrity sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg== + dependencies: + browserslist "^4.23.0" + postcss-value-parser "^4.2.0" + postcss-normalize-url@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" @@ -7670,6 +8956,13 @@ postcss-normalize-url@^5.1.0: normalize-url "^6.0.1" postcss-value-parser "^4.2.0" +postcss-normalize-url@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz#292792386be51a8de9a454cb7b5c58ae22db0f79" + integrity sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ== + dependencies: + postcss-value-parser "^4.2.0" + postcss-normalize-whitespace@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" @@ -7677,6 +8970,13 @@ postcss-normalize-whitespace@^5.1.1: dependencies: postcss-value-parser "^4.2.0" +postcss-normalize-whitespace@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz#fbb009e6ebd312f8b2efb225c2fcc7cf32b400cd" + integrity sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q== + dependencies: + postcss-value-parser "^4.2.0" + postcss-ordered-values@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" @@ -7685,6 +8985,14 @@ postcss-ordered-values@^5.1.3: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" +postcss-ordered-values@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz#366bb663919707093451ab70c3f99c05672aaae5" + integrity sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q== + dependencies: + cssnano-utils "^4.0.2" + postcss-value-parser "^4.2.0" + postcss-reduce-idents@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95" @@ -7692,6 +9000,13 @@ postcss-reduce-idents@^5.2.0: dependencies: postcss-value-parser "^4.2.0" +postcss-reduce-idents@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz#b0d9c84316d2a547714ebab523ec7d13704cd486" + integrity sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA== + dependencies: + postcss-value-parser "^4.2.0" + postcss-reduce-initial@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" @@ -7700,6 +9015,14 @@ postcss-reduce-initial@^5.1.2: browserslist "^4.21.4" caniuse-api "^3.0.0" +postcss-reduce-initial@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz#4401297d8e35cb6e92c8e9586963e267105586ba" + integrity sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw== + dependencies: + browserslist "^4.23.0" + caniuse-api "^3.0.0" + postcss-reduce-transforms@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" @@ -7707,6 +9030,21 @@ postcss-reduce-transforms@^5.1.0: dependencies: postcss-value-parser "^4.2.0" +postcss-reduce-transforms@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz#6fa2c586bdc091a7373caeee4be75a0f3e12965d" + integrity sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.16: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz#49694cb4e7c649299fea510a29fa6577104bcf53" + integrity sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: version "6.0.15" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" @@ -7722,6 +9060,13 @@ postcss-sort-media-queries@^4.4.1: dependencies: sort-css-media-queries "2.1.0" +postcss-sort-media-queries@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz#4556b3f982ef27d3bac526b99b6c0d3359a6cf97" + integrity sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA== + dependencies: + sort-css-media-queries "2.2.0" + postcss-svgo@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" @@ -7730,6 +9075,14 @@ postcss-svgo@^5.1.0: postcss-value-parser "^4.2.0" svgo "^2.7.0" +postcss-svgo@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.3.tgz#1d6e180d6df1fa8a3b30b729aaa9161e94f04eaa" + integrity sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^3.2.0" + postcss-unique-selectors@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" @@ -7737,6 +9090,13 @@ postcss-unique-selectors@^5.1.1: dependencies: postcss-selector-parser "^6.0.5" +postcss-unique-selectors@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz#983ab308896b4bf3f2baaf2336e14e52c11a2088" + integrity sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg== + dependencies: + postcss-selector-parser "^6.0.16" + postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" @@ -7747,6 +9107,11 @@ postcss-zindex@^5.1.0: resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff" integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== +postcss-zindex@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-6.0.2.tgz#e498304b83a8b165755f53db40e2ea65a99b56e1" + integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg== + postcss@^8.4.17, postcss@^8.4.21, postcss@^8.4.26, postcss@^8.4.33: version "8.4.35" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7" @@ -7756,6 +9121,15 @@ postcss@^8.4.17, postcss@^8.4.21, postcss@^8.4.26, postcss@^8.4.33: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.24, postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" @@ -7985,13 +9359,12 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" -"react-loadable@npm:@docusaurus/react-loadable@5.5.2": - version "5.5.2" - resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" - integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== +"react-loadable@npm:@docusaurus/react-loadable@6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz#de6c7f73c96542bd70786b8e522d535d69069dc4" + integrity sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ== dependencies: "@types/react" "*" - prop-types "^15.6.2" react-router-config@^5.1.1: version "5.1.1" @@ -8393,7 +9766,7 @@ schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0: +schema-utils@^4.0.0, schema-utils@^4.0.1: version "4.2.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== @@ -8622,6 +9995,14 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -8636,6 +10017,16 @@ sort-css-media-queries@2.1.0: resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz#7c85e06f79826baabb232f5560e9745d7a78c4ce" integrity sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA== +sort-css-media-queries@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz#aa33cf4a08e0225059448b6c40eddbf9f1c8334c" + integrity sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA== + +source-map-js@^1.0.1, source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -8822,6 +10213,14 @@ stylehacks@^5.1.1: browserslist "^4.21.4" postcss-selector-parser "^6.0.4" +stylehacks@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.1.1.tgz#543f91c10d17d00a440430362d419f79c25545a6" + integrity sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg== + dependencies: + browserslist "^4.23.0" + postcss-selector-parser "^6.0.16" + stylis@^4.1.3: version "4.3.1" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.1.tgz#ed8a9ebf9f76fe1e12d462f5cc3c4c980b23a7eb" @@ -8871,6 +10270,19 @@ svgo@^2.7.0, svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" +svgo@^3.0.2, svgo@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8" + integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + tapable@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" diff --git a/packages/cli/src/cmds/beacon/index.ts b/packages/cli/src/cmds/beacon/index.ts index b6d5c26f6fed..051fe10c2668 100644 --- a/packages/cli/src/cmds/beacon/index.ts +++ b/packages/cli/src/cmds/beacon/index.ts @@ -6,7 +6,7 @@ import {beaconHandler} from "./handler.js"; export const beacon: CliCommand = { command: "beacon", describe: "Run a beacon chain node", - docsFolder: "beacon-management", + docsFolder: "run/beacon-management", examples: [ { command: "beacon --network goerli", diff --git a/packages/cli/src/cmds/bootnode/index.ts b/packages/cli/src/cmds/bootnode/index.ts index c429f42b1fbe..6fc3c01247d6 100644 --- a/packages/cli/src/cmds/bootnode/index.ts +++ b/packages/cli/src/cmds/bootnode/index.ts @@ -7,7 +7,7 @@ export const bootnode: CliCommand = { command: "bootnode", describe: "Run a discv5 bootnode. This will NOT perform any beacon node functions, rather, it will run a discv5 service that allows nodes on the network to discover one another.", - docsFolder: "bootnode", + docsFolder: "run/bootnode", options: bootnodeOptions as CliCommandOptions, handler: bootnodeHandler, }; diff --git a/packages/cli/src/cmds/lightclient/index.ts b/packages/cli/src/cmds/lightclient/index.ts index e896a49abc56..6ee74e7d960f 100644 --- a/packages/cli/src/cmds/lightclient/index.ts +++ b/packages/cli/src/cmds/lightclient/index.ts @@ -6,7 +6,7 @@ import {lightclientHandler} from "./handler.js"; export const lightclient: CliCommand = { command: "lightclient", describe: "Run lightclient", - docsFolder: "lightclient-prover", + docsFolder: "libraries/lightclient-prover", examples: [ { command: "lightclient --network goerli", diff --git a/packages/cli/src/cmds/validator/index.ts b/packages/cli/src/cmds/validator/index.ts index c8b55bf4600a..f1ee2a458a3a 100644 --- a/packages/cli/src/cmds/validator/index.ts +++ b/packages/cli/src/cmds/validator/index.ts @@ -12,7 +12,7 @@ import {validatorHandler} from "./handler.js"; export const validator: CliCommand = { command: "validator", describe: "Run one or multiple validator clients", - docsFolder: "validator-management", + docsFolder: "run/validator-management", examples: [ { command: "validator --network goerli", diff --git a/scripts/prepare-docs.sh b/scripts/prepare-docs.sh index 8b52d7217d45..d2bac519a7f2 100755 --- a/scripts/prepare-docs.sh +++ b/scripts/prepare-docs.sh @@ -11,8 +11,8 @@ cp CONTRIBUTING.md $DOCS_DIR/pages/contribution/getting-started.md cp SECURITY.md $DOCS_DIR/pages/security.md # Copy package README.md to docs -cp -r packages/light-client/README.md $DOCS_DIR/pages/lightclient-prover/lightclient.md -cp -r packages/prover/README.md $DOCS_DIR/pages/lightclient-prover/prover.md +cp -r packages/light-client/README.md $DOCS_DIR/pages/libraries/lightclient-prover/lightclient.md +cp -r packages/prover/README.md $DOCS_DIR/pages/libraries/lightclient-prover/prover.md # Copy visual assets rm -rf $DOCS_DIR/pages/assets $DOCS_DIR/pages/images From c1532773c29248703cef16ece105deed6bd70745 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 7 Jun 2024 17:56:08 +0100 Subject: [PATCH 06/55] feat: enable debug apis by default (#6824) --- packages/beacon-node/src/api/rest/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/beacon-node/src/api/rest/index.ts b/packages/beacon-node/src/api/rest/index.ts index fa4a52c9c014..e46d1ab6d9bd 100644 --- a/packages/beacon-node/src/api/rest/index.ts +++ b/packages/beacon-node/src/api/rest/index.ts @@ -15,8 +15,7 @@ export type BeaconRestApiServerOpts = Omit & { export const beaconRestApiServerOpts: BeaconRestApiServerOpts = { enabled: true, - // ApiNamespace "debug" is not turned on by default - api: ["beacon", "config", "events", "node", "validator", "lightclient"], + api: ["beacon", "config", "debug", "events", "node", "validator", "lightclient"], address: "127.0.0.1", port: 9596, cors: "*", From f6d3bce80297023aaf890f201f5e94f1e701e46c Mon Sep 17 00:00:00 2001 From: NC Date: Sat, 8 Jun 2024 10:22:54 +0300 Subject: [PATCH 07/55] feat: add proposer boost reorg flag (#6652) * Second batch of changes * Wire proposer boost related code to block production * Update test * Update metrics * Update packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts Co-authored-by: twoeths * Address comment * Update packages/beacon-node/src/metrics/metrics/beacon.ts Co-authored-by: Nico Flaig * Compute hash treet root of updatedPrepareState * computeStateHashTreeRoot after prepareExecutionPayload * fix build issue * Fix spec test * lint * Remove Enabled suffix * Fix merge * Add alias * Update packages/cli/src/options/beaconNodeOptions/chain.ts Co-authored-by: Nico Flaig * chore: add predictProposerHead regen enum --------- Co-authored-by: twoeths Co-authored-by: Nico Flaig Co-authored-by: Tuyen Nguyen --- .../src/api/impl/validator/index.ts | 6 +- packages/beacon-node/src/chain/chain.ts | 58 +++++++- packages/beacon-node/src/chain/interface.ts | 12 ++ packages/beacon-node/src/chain/options.ts | 3 +- .../beacon-node/src/chain/prepareNextSlot.ts | 54 +++++-- .../beacon-node/src/chain/regen/interface.ts | 1 + .../beacon-node/src/metrics/metrics/beacon.ts | 18 ++- .../test/e2e/chain/proposerBoostReorg.test.ts | 136 ++++++++++++++++++ .../stateCache/nHistoricalStates.test.ts | 4 +- .../test/mocks/fork-choice/timeliness.ts | 24 ++++ .../test/mocks/mockedBeaconChain.ts | 2 + .../produceBlock/produceBlockBody.test.ts | 3 +- .../perf/chain/verifyImportBlocks.test.ts | 3 +- .../test/spec/presets/fork_choice.test.ts | 4 +- .../api/impl/validator/produceBlockV2.test.ts | 3 +- .../api/impl/validator/produceBlockV3.test.ts | 1 + .../test/unit/chain/prepareNextSlot.test.ts | 1 + packages/beacon-node/test/utils/logger.ts | 2 +- .../src/options/beaconNodeOptions/chain.ts | 19 ++- .../unit/options/beaconNodeOptions.test.ts | 6 +- .../fork-choice/src/forkChoice/forkChoice.ts | 59 +++++++- .../fork-choice/src/forkChoice/interface.ts | 31 ++-- packages/fork-choice/src/index.ts | 2 +- .../unit/forkChoice/getProposerHead.test.ts | 4 +- 24 files changed, 398 insertions(+), 58 deletions(-) create mode 100644 packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts create mode 100644 packages/beacon-node/test/mocks/fork-choice/timeliness.ts diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index f2a3f1faca0a..5a10b8337e9c 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -363,7 +363,7 @@ export function getValidatorApi({ // forkChoice.updateTime() might have already been called by the onSlot clock // handler, in which case this should just return. chain.forkChoice.updateTime(slot); - parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot); + parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot); } else { parentBlockRoot = inParentBlockRoot; } @@ -430,7 +430,7 @@ export function getValidatorApi({ // forkChoice.updateTime() might have already been called by the onSlot clock // handler, in which case this should just return. chain.forkChoice.updateTime(slot); - parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot); + parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot); } else { parentBlockRoot = inParentBlockRoot; } @@ -508,7 +508,7 @@ export function getValidatorApi({ // forkChoice.updateTime() might have already been called by the onSlot clock // handler, in which case this should just return. chain.forkChoice.updateTime(slot); - const parentBlockRoot = fromHexString(chain.recomputeForkChoiceHead().blockRoot); + const parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot); const fork = config.getForkName(slot); // set some sensible opts diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 047f7741c2f2..2f58962f3cc5 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -29,7 +29,7 @@ import { bellatrix, isBlindedBeaconBlock, } from "@lodestar/types"; -import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; +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"; @@ -45,7 +45,14 @@ import {isOptimisticBlock} from "../util/forkChoice.js"; import {BufferPool} from "../util/bufferPool.js"; import {BlockProcessor, ImportBlockOpts} from "./blocks/index.js"; import {ChainEventEmitter, ChainEvent} from "./emitter.js"; -import {IBeaconChain, ProposerPreparationData, BlockHash, StateGetOpts, CommonBlockBody} from "./interface.js"; +import { + IBeaconChain, + ProposerPreparationData, + BlockHash, + StateGetOpts, + CommonBlockBody, + FindHeadFnName, +} from "./interface.js"; import {IChainOptions} from "./options.js"; import {QueuedStateRegenerator, RegenCaller} from "./regen/index.js"; import {initializeForkChoice} from "./forkChoice/index.js"; @@ -279,7 +286,8 @@ export class BeaconChain implements IBeaconChain { clock.currentSlot, cachedState, opts, - this.justifiedBalancesGetter.bind(this) + this.justifiedBalancesGetter.bind(this), + logger ); const regen = new QueuedStateRegenerator({ config, @@ -703,12 +711,50 @@ export class BeaconChain implements IBeaconChain { recomputeForkChoiceHead(): ProtoBlock { this.metrics?.forkChoice.requests.inc(); - const timer = this.metrics?.forkChoice.findHead.startTimer(); + const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.recomputeForkChoiceHead}); try { - return this.forkChoice.updateHead(); + return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicialHead}).head; + } catch (e) { + this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetCanonicialHead}); + throw e; + } finally { + timer?.(); + } + } + + predictProposerHead(slot: Slot): ProtoBlock { + this.metrics?.forkChoice.requests.inc(); + const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.predictProposerHead}); + + try { + return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, slot}).head; + } catch (e) { + this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetPredictedProposerHead}); + throw e; + } finally { + timer?.(); + } + } + + getProposerHead(slot: Slot): ProtoBlock { + this.metrics?.forkChoice.requests.inc(); + const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.getProposerHead}); + const secFromSlot = this.clock.secFromSlot(slot); + + try { + const {head, isHeadTimely, notReorgedReason} = this.forkChoice.updateAndGetHead({ + mode: UpdateHeadOpt.GetProposerHead, + secFromSlot, + slot, + }); + + if (isHeadTimely && notReorgedReason !== undefined) { + this.metrics?.forkChoice.notReorgedReason.inc({reason: notReorgedReason}); + } + return head; } catch (e) { - this.metrics?.forkChoice.errors.inc(); + this.metrics?.forkChoice.errors.inc({entrypoint: UpdateHeadOpt.GetProposerHead}); throw e; } finally { timer?.(); diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index a277834d76b7..af7aeb47dd9c 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -64,6 +64,12 @@ export type StateGetOpts = { allowRegen: boolean; }; +export enum FindHeadFnName { + recomputeForkChoiceHead = "recomputeForkChoiceHead", + predictProposerHead = "predictProposerHead", + getProposerHead = "getProposerHead", +} + /** * The IBeaconChain service deals with processing incoming blocks, advancing a state transition * and applying the fork choice rule to update the chain head @@ -188,6 +194,12 @@ export interface IBeaconChain { recomputeForkChoiceHead(): ProtoBlock; + /** When proposerBoostReorg is enabled, this is called at slot n-1 to predict the head block to build on if we are proposing at slot n */ + predictProposerHead(slot: Slot): ProtoBlock; + + /** When proposerBoostReorg is enabled and we are proposing a block, this is called to determine which head block to build on */ + getProposerHead(slot: Slot): ProtoBlock; + waitForBlock(slot: Slot, root: RootHex): Promise; updateBeaconProposerData(epoch: Epoch, proposers: ProposerPreparationData[]): Promise; diff --git a/packages/beacon-node/src/chain/options.ts b/packages/beacon-node/src/chain/options.ts index c0d32449b072..7c7cfcdde75b 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -95,7 +95,8 @@ export const defaultChainOptions: IChainOptions = { blsVerifyAllMainThread: false, blsVerifyAllMultiThread: false, disableBlsBatchVerify: false, - proposerBoostEnabled: true, + proposerBoost: true, + proposerBoostReorg: false, computeUnrealized: true, safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index c155c3198269..3f730df3bf1d 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -2,7 +2,9 @@ import { computeEpochAtSlot, isExecutionStateType, computeTimeAtSlot, + CachedBeaconStateExecutions, StateHashTreeRootSource, + CachedBeaconStateAllForks, } from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; import {ForkSeq, SLOTS_PER_EPOCH, ForkExecution} from "@lodestar/params"; @@ -113,14 +115,6 @@ export class PrepareNextSlotScheduler { RegenCaller.precomputeEpoch ); - // cache HashObjects for faster hashTreeRoot() later, especially for computeNewStateRoot() if we need to produce a block at slot 0 of epoch - // see https://github.com/ChainSafe/lodestar/issues/6194 - const hashTreeRootTimer = this.metrics?.stateHashTreeRootTime.startTimer({ - source: StateHashTreeRootSource.prepareNextSlot, - }); - prepareState.hashTreeRoot(); - hashTreeRootTimer?.(); - // assuming there is no reorg, it caches the checkpoint state & helps avoid doing a full state transition in the next slot // + when gossip block comes, we need to validate and run state transition // + if next slot is a skipped slot, it'd help getting target checkpoint state faster to validate attestations @@ -144,7 +138,31 @@ export class PrepareNextSlotScheduler { if (isExecutionStateType(prepareState)) { const proposerIndex = prepareState.epochCtx.getBeaconProposer(prepareSlot); const feeRecipient = this.chain.beaconProposerCache.get(proposerIndex); + let updatedPrepareState = prepareState; + let updatedHeadRoot = headRoot; + if (feeRecipient) { + // If we are proposing next slot, we need to predict if we can proposer-boost-reorg or not + const {slot: proposerHeadSlot, blockRoot: proposerHeadRoot} = this.chain.predictProposerHead(clockSlot); + + // If we predict we can reorg, update prepareState with proposer head block + if (proposerHeadRoot !== headRoot || proposerHeadSlot !== headSlot) { + this.logger.verbose("Weak head detected. May build on this block instead:", { + proposerHeadSlot, + proposerHeadRoot, + headSlot, + headRoot, + }); + this.metrics?.weakHeadDetected.inc(); + updatedPrepareState = (await this.chain.regen.getBlockSlotState( + proposerHeadRoot, + prepareSlot, + {dontTransferCache: !isEpochTransition}, + RegenCaller.predictProposerHead + )) as CachedBeaconStateExecutions; + updatedHeadRoot = proposerHeadRoot; + } + // Update the builder status, if enabled shoot an api call to check status this.chain.updateBuilderStatus(clockSlot); if (this.chain.executionBuilder?.status) { @@ -167,10 +185,10 @@ export class PrepareNextSlotScheduler { this.chain, this.logger, fork as ForkExecution, // State is of execution type - fromHex(headRoot), + fromHex(updatedHeadRoot), safeBlockHash, finalizedBlockHash, - prepareState, + updatedPrepareState, feeRecipient ); this.logger.verbose("PrepareNextSlotScheduler prepared new payload", { @@ -180,10 +198,12 @@ export class PrepareNextSlotScheduler { }); } + this.computeStateHashTreeRoot(updatedPrepareState); + // If emitPayloadAttributes is true emit a SSE payloadAttributes event if (this.chain.opts.emitPayloadAttributes === true) { const data = await getPayloadAttributesForSSE(fork as ForkExecution, this.chain, { - prepareState, + prepareState: updatedPrepareState, prepareSlot, parentBlockRoot: fromHex(headRoot), // The likely consumers of this API are builders and will anyway ignore the @@ -192,6 +212,8 @@ export class PrepareNextSlotScheduler { }); this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork}); } + } else { + this.computeStateHashTreeRoot(prepareState); } } catch (e) { if (!isErrorAborted(e) && !isQueueErrorAborted(e)) { @@ -200,4 +222,14 @@ export class PrepareNextSlotScheduler { } } }; + + computeStateHashTreeRoot(state: CachedBeaconStateAllForks): void { + // cache HashObjects for faster hashTreeRoot() later, especially for computeNewStateRoot() if we need to produce a block at slot 0 of epoch + // see https://github.com/ChainSafe/lodestar/issues/6194 + const hashTreeRootTimer = this.metrics?.stateHashTreeRootTime.startTimer({ + source: StateHashTreeRootSource.prepareNextSlot, + }); + state.hashTreeRoot(); + hashTreeRootTimer?.(); + } } diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index 650d92143a8e..a1021de4aeab 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -11,6 +11,7 @@ export enum RegenCaller { validateGossipBlock = "validateGossipBlock", validateGossipBlob = "validateGossipBlob", precomputeEpoch = "precomputeEpoch", + predictProposerHead = "predictProposerHead", produceAttestationData = "produceAttestationData", processBlocksInEpoch = "processBlocksInEpoch", validateGossipAggregateAndProof = "validateGossipAggregateAndProof", diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index 9366174ef6c6..141121de9079 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -1,4 +1,6 @@ import {ProducedBlockSource} from "@lodestar/types"; +import {NotReorgedReason} from "@lodestar/fork-choice/lib/forkChoice/interface.js"; +import {UpdateHeadOpt} from "@lodestar/fork-choice"; import {RegistryMetricCreator} from "../utils/registryMetricCreator.js"; import {BlockProductionStep, PayloadPreparationType} from "../../chain/produceBlock/index.js"; @@ -57,18 +59,20 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { // Non-spec'ed forkChoice: { - findHead: register.histogram({ + findHead: register.histogram<{entrypoint: string}>({ name: "beacon_fork_choice_find_head_seconds", help: "Time taken to find head in seconds", buckets: [0.1, 1, 10], + labelNames: ["entrypoint"], }), requests: register.gauge({ name: "beacon_fork_choice_requests_total", help: "Count of occasions where fork choice has tried to find a head", }), - errors: register.gauge({ + errors: register.gauge<{entrypoint: UpdateHeadOpt}>({ name: "beacon_fork_choice_errors_total", help: "Count of occasions where fork choice has returned an error when trying to find a head", + labelNames: ["entrypoint"], }), changedHead: register.gauge({ name: "beacon_fork_choice_changed_head_total", @@ -109,6 +113,11 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { name: "beacon_fork_choice_indices_count", help: "Current count of indices in fork choice data structures", }), + notReorgedReason: register.gauge<{reason: NotReorgedReason}>({ + name: "beacon_fork_choice_not_reorged_reason_total", + help: "Reason why the current head is not re-orged out", + labelNames: ["reason"], + }), }, parentBlockDistance: register.histogram({ @@ -198,5 +207,10 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { name: "beacon_clock_epoch", help: "Current clock epoch", }), + + weakHeadDetected: register.gauge({ + name: "beacon_weak_head_detected_total", + help: "Detected current head block is weak. May reorg it out when proposing next slot. See proposer boost reorg for more", + }), }; } diff --git a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts new file mode 100644 index 000000000000..145f378935fe --- /dev/null +++ b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts @@ -0,0 +1,136 @@ +import {describe, it, afterEach, expect} from "vitest"; +import {SLOTS_PER_EPOCH} from "@lodestar/params"; +import {TimestampFormatCode} from "@lodestar/logger"; +import {ChainConfig} from "@lodestar/config"; +import {RootHex, Slot} from "@lodestar/types"; +import {routes} from "@lodestar/api"; +import {toHexString} from "@lodestar/utils"; +import {LogLevel, TestLoggerOpts, testLogger} from "../../utils/logger.js"; +import {getDevBeaconNode} from "../../utils/node/beacon.js"; +import {TimelinessForkChoice} from "../../mocks/fork-choice/timeliness.js"; +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, + }; + + 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, + 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()))); + + 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.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), + }); + }); + }, + {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 7de3f14435e9..170081e4dcd8 100644 --- a/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts +++ b/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts @@ -295,7 +295,7 @@ describe( chain: { blsVerifyAllMainThread: true, forkchoiceConstructor: ReorgedForkChoice, - proposerBoostEnabled: true, + proposerBoost: true, }, }, validatorCount, @@ -318,7 +318,7 @@ describe( nHistoricalStates: true, maxBlockStates, maxCPStateEpochsInMemory, - proposerBoostEnabled: true, + proposerBoost: true, }, metrics: {enabled: true}, }, diff --git a/packages/beacon-node/test/mocks/fork-choice/timeliness.ts b/packages/beacon-node/test/mocks/fork-choice/timeliness.ts new file mode 100644 index 000000000000..72b3ff66a084 --- /dev/null +++ b/packages/beacon-node/test/mocks/fork-choice/timeliness.ts @@ -0,0 +1,24 @@ +import {ForkChoice} from "@lodestar/fork-choice"; +import {Slot, allForks} from "@lodestar/types"; + +/** + * A specific forkchoice implementation to mark some blocks as timely or not. + */ +export class TimelinessForkChoice extends ForkChoice { + /** + * These need to be in the constructor, however we want to keep the constructor signature the same. + * So they are set after construction in the test instead. + */ + lateSlot: Slot | undefined; + + /** + * This is to mark the `lateSlot` as not timely. + */ + protected isBlockTimely(block: allForks.BeaconBlock, blockDelaySec: number): boolean { + if (block.slot === this.lateSlot) { + return false; + } + + return super.isBlockTimely(block, blockDelaySec); + } +} diff --git a/packages/beacon-node/test/mocks/mockedBeaconChain.ts b/packages/beacon-node/test/mocks/mockedBeaconChain.ts index aa8228dcece0..39b62b597076 100644 --- a/packages/beacon-node/test/mocks/mockedBeaconChain.ts +++ b/packages/beacon-node/test/mocks/mockedBeaconChain.ts @@ -128,10 +128,12 @@ vi.mock("../../src/chain/chain.js", async (importActual) => { beaconProposerCache: new BeaconProposerCache(), shufflingCache: new ShufflingCache(), produceCommonBlockBody: vi.fn(), + getProposerHead: vi.fn(), produceBlock: vi.fn(), produceBlindedBlock: vi.fn(), getCanonicalBlockAtSlot: vi.fn(), recomputeForkChoiceHead: vi.fn(), + predictProposerHead: vi.fn(), getHeadStateAtCurrentEpoch: vi.fn(), getHeadState: vi.fn(), updateBuilderStatus: vi.fn(), diff --git a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts index 96dda3acaece..7bf8c2f7252f 100644 --- a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts +++ b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts @@ -28,7 +28,8 @@ describe("produceBlockBody", () => { state = stateOg.clone(); chain = new BeaconChain( { - proposerBoostEnabled: true, + proposerBoost: true, + proposerBoostReorg: false, computeUnrealized: false, safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, disableArchiveOnCheckpoint: true, diff --git a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts index 41d8aa76865b..19d33072bd7b 100644 --- a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts +++ b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts @@ -84,7 +84,8 @@ describe.skip("verify+import blocks - range sync perf test", () => { const state = stateOg.value.clone(); const chain = new BeaconChain( { - proposerBoostEnabled: true, + proposerBoost: true, + proposerBoostReorg: false, computeUnrealized: false, safeSlotsToImportOptimistically: SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, disableArchiveOnCheckpoint: true, diff --git a/packages/beacon-node/test/spec/presets/fork_choice.test.ts b/packages/beacon-node/test/spec/presets/fork_choice.test.ts index 997299c4e0e7..3b5d83bbeb95 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -97,6 +97,8 @@ const forkChoiceTest = // we don't use these in fork choice spec tests disablePrepareNextSlot: true, assertCorrectProgressiveBalances, + proposerBoost: true, + proposerBoostReorg: true, }, { config: createBeaconConfig(config, state.genesisValidatorsRoot), @@ -270,7 +272,7 @@ const forkChoiceTest = logger.debug(`Step ${i}/${stepsLen} check`); // Forkchoice head is computed lazily only on request - const head = chain.forkChoice.updateHead(); + const head = (chain.forkChoice as ForkChoice).updateHead(); const proposerBootRoot = (chain.forkChoice as ForkChoice).getProposerBoostRoot(); if (step.checks.head !== undefined) { 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 1ec3f738669d..370a25d7cc92 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 @@ -47,6 +47,7 @@ describe("api/validator - produceBlockV2", function () { const graffiti = "a".repeat(32); const feeRecipient = "0xcccccccccccccccccccccccccccccccccccccccc"; + modules.chain.getProposerHead.mockReturnValue(generateProtoBlock({blockRoot: toHexString(parentBlockRoot)})); modules.chain.recomputeForkChoiceHead.mockReturnValue( generateProtoBlock({blockRoot: toHexString(parentBlockRoot)}) ); @@ -87,7 +88,7 @@ describe("api/validator - produceBlockV2", function () { const feeRecipient = "0xccccccccccccccccccccccccccccccccccccccaa"; const headSlot = 0; - modules.forkChoice.getHead.mockReturnValue(generateProtoBlock({slot: headSlot})); + modules.chain.getProposerHead.mockReturnValue(generateProtoBlock({slot: headSlot})); modules.chain.recomputeForkChoiceHead.mockReturnValue(generateProtoBlock({slot: headSlot})); modules.chain["opPool"].getSlashingsAndExits.mockReturnValue([[], [], [], []]); 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 d182cfeb537e..4adb07cd154b 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 @@ -87,6 +87,7 @@ describe("api/validator - produceBlockV3", function () { modules.chain.recomputeForkChoiceHead.mockReturnValue({ blockRoot: toHexString(fullBlock.parentRoot), } as ProtoBlock); + modules.chain.getProposerHead.mockReturnValue({blockRoot: toHexString(fullBlock.parentRoot)} as ProtoBlock); if (enginePayloadValue !== null) { const commonBlockBody: CommonBlockBody = { diff --git a/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts b/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts index 6d1be3fa8dd5..652749492240 100644 --- a/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts +++ b/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts @@ -120,6 +120,7 @@ describe("PrepareNextSlot scheduler", () => { chainStub.emitter.on(routes.events.EventType.payloadAttributes, spy); getForkStub.mockReturnValue(ForkName.bellatrix); chainStub.recomputeForkChoiceHead.mockReturnValue({...zeroProtoBlock, slot: SLOTS_PER_EPOCH - 3} as ProtoBlock); + chainStub.predictProposerHead.mockReturnValue({...zeroProtoBlock, slot: SLOTS_PER_EPOCH - 3} as ProtoBlock); forkChoiceStub.getJustifiedBlock.mockReturnValue({} as ProtoBlock); forkChoiceStub.getFinalizedBlock.mockReturnValue({} as ProtoBlock); updateBuilderStatus.mockReturnValue(void 0); diff --git a/packages/beacon-node/test/utils/logger.ts b/packages/beacon-node/test/utils/logger.ts index 1c1526514565..10f27565216f 100644 --- a/packages/beacon-node/test/utils/logger.ts +++ b/packages/beacon-node/test/utils/logger.ts @@ -21,6 +21,6 @@ export const testLogger = (module?: string, opts?: TestLoggerOpts): LoggerNode = opts.module = module; } const level = getEnvLogLevel(); - opts.level = level ?? LogLevel.info; + opts.level = level ?? opts.level ?? LogLevel.info; return getNodeLogger(opts); }; diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index 0fee440a792b..aae97b6db68f 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -12,7 +12,8 @@ export type ChainArgs = { // No need to define chain.persistInvalidSszObjects as part of ChainArgs // as this is defined as part of BeaconPaths // "chain.persistInvalidSszObjectsDir": string; - "chain.proposerBoostEnabled"?: boolean; + "chain.proposerBoost"?: boolean; + "chain.proposerBoostReorg"?: boolean; "chain.disableImportExecutionFcU"?: boolean; "chain.preaggregateSlotDistance"?: number; "chain.attDataCacheSlotDistance"?: number; @@ -43,7 +44,8 @@ export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] { persistInvalidSszObjects: args["chain.persistInvalidSszObjects"], // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any persistInvalidSszObjectsDir: undefined as any, - proposerBoostEnabled: args["chain.proposerBoostEnabled"], + proposerBoost: args["chain.proposerBoost"], + proposerBoostReorg: args["chain.proposerBoostReorg"], disableImportExecutionFcU: args["chain.disableImportExecutionFcU"], preaggregateSlotDistance: args["chain.preaggregateSlotDistance"], attDataCacheSlotDistance: args["chain.attDataCacheSlotDistance"], @@ -123,11 +125,20 @@ Will double processing times. Use only for debugging purposes.", group: "chain", }, - "chain.proposerBoostEnabled": { + "chain.proposerBoost": { + alias: ["chain.proposerBoostEnabled"], hidden: true, type: "boolean", description: "Enable proposer boost to reward a timely block", - defaultDescription: String(defaultOptions.chain.proposerBoostEnabled), + defaultDescription: String(defaultOptions.chain.proposerBoost), + group: "chain", + }, + + "chain.proposerBoostReorg": { + hidden: true, + type: "boolean", + description: "Enable proposer boost reorg to reorg out a late block", + defaultDescription: String(defaultOptions.chain.proposerBoostReorg), group: "chain", }, diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index f31cc604f775..d74ae73b966f 100644 --- a/packages/cli/test/unit/options/beaconNodeOptions.test.ts +++ b/packages/cli/test/unit/options/beaconNodeOptions.test.ts @@ -23,7 +23,8 @@ describe("options / beaconNodeOptions", () => { "chain.disableBlsBatchVerify": true, "chain.persistProducedBlocks": true, "chain.persistInvalidSszObjects": true, - "chain.proposerBoostEnabled": false, + "chain.proposerBoost": false, + "chain.proposerBoostReorg": false, "chain.disableImportExecutionFcU": false, "chain.preaggregateSlotDistance": 1, "chain.attDataCacheSlotDistance": 2, @@ -129,7 +130,8 @@ describe("options / beaconNodeOptions", () => { disableBlsBatchVerify: true, persistProducedBlocks: true, persistInvalidSszObjects: true, - proposerBoostEnabled: false, + proposerBoost: false, + proposerBoostReorg: false, disableImportExecutionFcU: false, preaggregateSlotDistance: 1, attDataCacheSlotDistance: 2, diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 8ba3156a1b9a..b2e1a5314012 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -45,10 +45,22 @@ import { import {IForkChoiceStore, CheckpointWithHex, toCheckpointWithHex, JustifiedBalances} from "./store.js"; export type ForkChoiceOpts = { - proposerBoostEnabled?: boolean; + proposerBoost?: boolean; + proposerBoostReorg?: boolean; computeUnrealized?: boolean; }; +export enum UpdateHeadOpt { + GetCanonicialHead = "getCanonicialHead", // Skip getProposerHead + GetProposerHead = "getProposerHead", // With getProposerHead + GetPredictedProposerHead = "getPredictedProposerHead", // With predictProposerHead +} + +export type UpdateAndGetHeadOpt = + | {mode: UpdateHeadOpt.GetCanonicialHead} + | {mode: UpdateHeadOpt.GetProposerHead; secFromSlot: number; slot: Slot} + | {mode: UpdateHeadOpt.GetPredictedProposerHead; slot: Slot}; + /** * Provides an implementation of "Ethereum Consensus -- Beacon Chain Fork Choice": * @@ -156,6 +168,41 @@ export class ForkChoice implements IForkChoice { return this.head; } + /** + * + * A multiplexer to wrap around the traditional `updateHead()` according to the scenario + * Scenarios as follow: + * Prepare to propose in the next slot: getHead() -> predictProposerHead() + * Proposing in the current slot: updateHead() -> getProposerHead() + * Others eg. initializing forkchoice, importBlock: updateHead() + * + * Only `GetProposerHead` returns additional field `isHeadTimely` and `notReorgedReason` for metrics purpose + */ + updateAndGetHead(opt: UpdateAndGetHeadOpt): { + head: ProtoBlock; + isHeadTimely?: boolean; + notReorgedReason?: NotReorgedReason; + } { + const {mode} = opt; + + const canonicialHeadBlock = mode === UpdateHeadOpt.GetPredictedProposerHead ? this.getHead() : this.updateHead(); + switch (mode) { + case UpdateHeadOpt.GetPredictedProposerHead: + return {head: this.predictProposerHead(canonicialHeadBlock, opt.slot)}; + case UpdateHeadOpt.GetProposerHead: { + const { + proposerHead: head, + isHeadTimely, + notReorgedReason, + } = this.getProposerHead(canonicialHeadBlock, opt.secFromSlot, opt.slot); + return {head, isHeadTimely, notReorgedReason}; + } + case UpdateHeadOpt.GetCanonicialHead: + default: + return {head: canonicialHeadBlock}; + } + } + /** * Get the proposer boost root */ @@ -176,7 +223,7 @@ export class ForkChoice implements IForkChoice { */ predictProposerHead(headBlock: ProtoBlock, currentSlot?: Slot): ProtoBlock { // Skip re-org attempt if proposer boost (reorg) are disabled - if (!this.opts?.proposerBoostEnabled) { + if (!this.opts?.proposerBoost || !this.opts?.proposerBoostReorg) { this.logger?.verbose("No proposer boot reorg prediction since the related flags are disabled"); return headBlock; } @@ -209,7 +256,7 @@ export class ForkChoice implements IForkChoice { * * This function takes in the canonical head block and determine the proposer head (canonical head block or its parent) * https://github.com/ethereum/consensus-specs/pull/3034 for info about proposer boost reorg - * This function should only be called during block proposal and only be called after `updateHead()` + * This function should only be called during block proposal and only be called after `updateHead()` in `updateAndGetHead()` * * Same as https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/phase0/fork-choice.md#get_proposer_head */ @@ -222,7 +269,7 @@ export class ForkChoice implements IForkChoice { let proposerHead = headBlock; // Skip re-org attempt if proposer boost (reorg) are disabled - if (!this.opts?.proposerBoostEnabled) { + if (!this.opts?.proposerBoost || !this.opts?.proposerBoostReorg) { this.logger?.verbose("No proposer boot reorg attempt since the related flags are disabled"); return {proposerHead, isHeadTimely, notReorgedReason: NotReorgedReason.ProposerBoostReorgDisabled}; } @@ -328,7 +375,7 @@ export class ForkChoice implements IForkChoice { * starting from the proposerIndex */ let proposerBoost: {root: RootHex; score: number} | null = null; - if (this.opts?.proposerBoostEnabled && this.proposerBoostRoot) { + if (this.opts?.proposerBoost && this.proposerBoostRoot) { const proposerBoostScore = this.justifiedProposerBoostScore ?? getCommitteeFraction(this.fcStore.justified.totalBalance, { @@ -486,7 +533,7 @@ export class ForkChoice implements IForkChoice { // before attesting interval = before 1st interval const isTimely = this.isBlockTimely(block, blockDelaySec); if ( - this.opts?.proposerBoostEnabled && + this.opts?.proposerBoost && isTimely && // only boost the first block we see this.proposerBoostRoot === null diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index e4d087deab3a..d91a338bbca5 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -3,6 +3,7 @@ import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, Slot, ValidatorIndex, phase0, allForks, Root, RootHex} from "@lodestar/types"; import {ProtoBlock, MaybeValidExecutionStatus, LVHExecResponse, ProtoNode} from "../protoArray/interface.js"; import {CheckpointWithHex} from "./store.js"; +import {UpdateAndGetHeadOpt} from "./forkChoice.js"; export type CheckpointHex = { epoch: Epoch; @@ -43,18 +44,18 @@ export type AncestorResult = // Reason for not proposer boost reorging export enum NotReorgedReason { - HeadBlockIsTimely, - ParentBlockNotAvailable, - ProposerBoostReorgDisabled, - NotShufflingStable, - NotFFGCompetitive, - ChainLongUnfinality, - ParentBlockDistanceMoreThanOneSlot, - ReorgMoreThanOneSlot, - ProposerBoostNotWornOff, - HeadBlockNotWeak, - ParentBlockNotStrong, - NotProposingOnTime, + HeadBlockIsTimely = "headBlockIsTimely", + ParentBlockNotAvailable = "parentBlockNotAvailable", + ProposerBoostReorgDisabled = "proposerBoostReorgDisabled", + NotShufflingStable = "notShufflingStable", + NotFFGCompetitive = "notFFGCompetitive", + ChainLongUnfinality = "chainLongUnfinality", + ParentBlockDistanceMoreThanOneSlot = "parentBlockDistanceMoreThanOneSlot", + ReorgMoreThanOneSlot = "reorgMoreThanOneSlot", + ProposerBoostNotWornOff = "proposerBoostNotWornOff", + HeadBlockNotWeak = "headBlockNotWeak", + ParentBlockNotStrong = "ParentBlockNotStrong", + NotProposingOnTime = "notProposingOnTime", } export type ForkChoiceMetrics = { @@ -92,7 +93,11 @@ export interface IForkChoice { */ getHeadRoot(): RootHex; getHead(): ProtoBlock; - updateHead(): ProtoBlock; + updateAndGetHead(mode: UpdateAndGetHeadOpt): { + head: ProtoBlock; + isHeadTimely?: boolean; + notReorgedReason?: NotReorgedReason; + }; /** * Retrieves all possible chain heads (leaves of fork choice tree). */ diff --git a/packages/fork-choice/src/index.ts b/packages/fork-choice/src/index.ts index ff0711599a54..12b678d7db2b 100644 --- a/packages/fork-choice/src/index.ts +++ b/packages/fork-choice/src/index.ts @@ -9,7 +9,7 @@ export type { } from "./protoArray/interface.js"; export {ExecutionStatus} from "./protoArray/interface.js"; -export {ForkChoice, type ForkChoiceOpts, assertValidTerminalPowBlock} from "./forkChoice/forkChoice.js"; +export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt, assertValidTerminalPowBlock} from "./forkChoice/forkChoice.js"; export { type IForkChoice, type PowBlockHex, diff --git a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts index 661d3b45ed68..25d539a5e33b 100644 --- a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts @@ -235,8 +235,8 @@ describe("Forkchoice / GetProposerHead", function () { }); const forkChoice = new ForkChoice(config, fcStore, protoArr, { - proposerBoostEnabled: true, - // proposerBoostReorgEnabled: true, + proposerBoost: true, + proposerBoostReorg: true, }); const {proposerHead, isHeadTimely, notReorgedReason} = forkChoice.getProposerHead( From 14855ea72fa7c46c984ed13078aac1c21858b993 Mon Sep 17 00:00:00 2001 From: Julien Date: Sun, 9 Jun 2024 00:31:36 -0700 Subject: [PATCH 08/55] fix: incorrect variable access (#6862) --- packages/beacon-node/src/network/gossip/encoding.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/beacon-node/src/network/gossip/encoding.ts b/packages/beacon-node/src/network/gossip/encoding.ts index 3b87f42747ab..53847f56df7d 100644 --- a/packages/beacon-node/src/network/gossip/encoding.ts +++ b/packages/beacon-node/src/network/gossip/encoding.ts @@ -3,6 +3,7 @@ import xxhashFactory from "xxhash-wasm"; import {Message} from "@libp2p/interface"; import {digest} from "@chainsafe/as-sha256"; import {RPC} from "@chainsafe/libp2p-gossipsub/message"; +import {DataTransform} from "@chainsafe/libp2p-gossipsub/types"; import {intToBytes, toHex} from "@lodestar/utils"; import {ForkName} from "@lodestar/params"; import {MESSAGE_DOMAIN_VALID_SNAPPY} from "./constants.js"; @@ -61,7 +62,7 @@ export function msgIdFn(gossipTopicCache: GossipTopicCache, msg: Message): Uint8 return digest(Buffer.concat(vec)).subarray(0, 20); } -export class DataTransformSnappy { +export class DataTransformSnappy implements DataTransform { constructor( private readonly gossipTopicCache: GossipTopicCache, private readonly maxSizePerMessage: number @@ -96,9 +97,9 @@ export class DataTransformSnappy { * Takes the data to be published (a topic and associated data) transforms the data. The * transformed data will then be used to create a `RawGossipsubMessage` to be sent to peers. */ - outboundTransform(topicStr: string, data: Uint8Array): Uint8Array { + outboundTransform(_topicStr: string, data: Uint8Array): Uint8Array { if (data.length > this.maxSizePerMessage) { - throw Error(`ssz_snappy encoded data length ${length} > ${this.maxSizePerMessage}`); + throw Error(`ssz_snappy encoded data length ${data.length} > ${this.maxSizePerMessage}`); } // No need to parse topic, everything is snappy compressed return compress(data); From f8593a9a81f7744f4fad2de3e00d4ae04826f9ce Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 10 Jun 2024 15:16:27 +0100 Subject: [PATCH 09/55] feat: provide first-class ssz support on api (#6749) * Add config route definitions * Add debug route definitions * Add events route description * Add lightclient route definitions * Flatten function params * Type safety for optional params in write / parse req * Method args are optional if only optional props * Fix genesisValidatorsRoot type issue * Revert requiring all params in write / parse req * Update http client errors * Add lodestar route definitions * Add node route definitions * Add proof route definitions * Add builder route definitions * Add validator route definitions * Application method response can be void * Generic options can be passed to application methods * Default endpoint request type has body property * Improve types of transform methods * Export server types from index (to be removed) * Update config api impl * Update lightclient api impl * Update events api impl * Update lodestar api impl * Update proof api impl * Update node api impl * Update debug api impl * Update state api impl * Update pool api impl * Update blocks api impl * Partially update validator api impl * Update beacon routes export * Align submitPoolBlsToExecutionChange method args * Filters are always a object * Update errors messages * Add beacon client methods * Add missing routeId label to stream time metric * Fix json casing in codecs * Apply remaining changes from #6227 * Produce block apis only have version meta * Add block values meta to all produce block apis * Apply changes from #6337 * Handle unsafe version in WithMeta and WithVersion * Restore server api error * Update fastify route types * Update server routes / handlers * Remove unnecessary type cast * Restore per route clients * Fix beacon route types * Remove option to patch fetch from http client * Update eventstream client, remove fetch override Fallback does not work like this, see #6180 for proper solution * Use StringType for validator status until #6059 * Remove empty fetch.ts file * Add a few todos * Update builder client and server routes * Update beacon exports * Update api index exports * Update builder index imports * Improve type safety of schema definitions * Add headers to fastify schema * Fix schema definition type * Add missing schemas to route definitions * Fix response codec type * Remove response codec type casts * Fix casing in json only codec * Reuse EmptyResponseCodec * Update base rest api server * Update keymanager routes, client and server * Reuse data types in keymanager impl * Do not await setting headers, not a promise * Improve type safety of empty codecs * Only require to implement supported req methods * Handle requests that only support one format * Handle responses that only support one format * Add json / ssz only req codecs * Update only support errors * Fix assertion * Set correct accept header if only supports one format * Fix eslint / prettier issues * More formatting fixes * Fix fallback request retries in case of http errors * Formatting of res.error * Add add retry functionality to http client (from #6387) * Update rewards routes and server (#6178 and #6260) * Allow to omit body in ssz req if not defined * Always set metadata headers in response * Cache wire format in api response * Only call raw body for json meta * Update api package tests (wip) * Test json and ssz format in generic server tests * Add a bunch of todos * Fix a few broken route definitions * Fix partial config test * Another todo note * Stringify body of json requests * Override default response json method * Validate external consensus version headers in request * Add error handling todo * Skip body schema validation for ssz request bodies * Clean up generic server tests * Pass node, proof, events generic tests * Use enum for media types * Fix a bunch of route definitions * Add justified to blockid type * Properly handle booleans, remove block values codec * Create Uint8Array test data without allocating Buffer * Let fastify handle Buffer conversion * Convert Buffer to Uint8Array in content type parser * Fix build issues * Fix fork type in builder routes * Add some notes * Properly parse request headers * Fix incorrect type assumptions in transform * Generic server tests are passing (except lightclient) * Correctly handle APIs with empty responses * Update getHeader return type to reflect no bid responses * Do not append '?' to URL if query string is empty * Let server handler set status code for parsing errors * Remove unused import * Rename function, request specific * Completely drop ssz support from getSpec * Spec tests are passing against latest releases * Drop unused fastify route config * Drop ssz request from builder routes, not yet supported * Remove import * Apply change from #6695 * Update execution optimistic meta * Apply changes from #6645 * Add workaround to fix epoch committees type issue * Add todo to fix inefficient state conversion * Convert committee to normal array * Apply changes from #6655 * Align args of validators endpoints * Convert indices to str in rewards apis * Update api spec version of README badges * Revert table formatting changes * Make this accessible for class-basd API implementations * Throw err if metadata is accessed for failed response * Add assertOk to api response * Tweak api error message * Update operationIds match spec value * Add missing version to blob sidecars metadata * Test headers and ssz bodies against spec * Minor reordering of code in spec parsing * submitBlindedBlock throws err if fork is not execution * responseOk might be undefined * Remove statusOk from route definition * Remove stale comment * Less build errors in beacon-node * getBlobSidecars return version from server impl * Update validator produce block impl * More expressive pool method args * Application methods might be undefined in mock implementations * Adress open TODOs in server handler * Api response methods are synchronous now * Fix all remaining build issues * Use more performant from/toHex in server api impls * Clean up some TODOs * Fix ApiError type * Errors related to parsing return a 400 status code * Simplify method binding * Forward api context to application methods * There is no easy way to make generic opts typesafe * Better separation of server / client code * Fix comment about missing builder bid * Remove todo, not worth the change / extra indentation * Rename route definitions functions * Return 400 if data passed to keymanager is invalid * Properly handle response metadata headers * Fix lint issues * Add header jsdoc * Move metadata related code into separate file * Remove ssz from POST requests without body * Only set content-type header if body exists * Fix headers extra * POST requests without body are handled similar to GET requests * Fix http client options tests * Improve validation and type safety of JSON metadata * Add type guard for request without body * Differentiate based on body instead of GET vs POST * More renaming * Simplify RequestCode type * Review routes, improve validation * Remaining local diff * Fix accept header handling if only support one wire format * Update 406 error to more closely match spec example * Enforce version header via custom check instead of schema * Use ssz as default request wire format * Log failure to receive head event to verbose * Do not set default value for context * Update getClient return type to better align with method name * Consistent pattern to get route definitions * Dedupe api client type for builder and keymanager * Fix fallback logic if server returns http error * Update head event error logging * Retry 415 errors with JSON and cache SSZ not supported * Use fetch spy to assert call times * Update comment * Update getLightClientUpdatesByRange endpoint meta * Do not forward ssz bytes of blinded block to publishBlock * Fix lightclient e2e tests * Version header in publishBlock api is optional * Reduce type duplication * Add option to override request init in route definition * Add JsonOnlyResp codec * Validate boolean str value from headers * Document default wire formats * Simplify merging of inits in http client * Remove type hacks from fetchBeaconHealth * Reduce call stack in http client * Add .ssz() equivalent method for json to api response * More http client tests * Ensure topics query is provided to eventstream api * Validate request content type in handler Fastify does not cover all edge cases * Review routes, fix param docs, no empty comments * Fix typo * Add note about builder spec not supporting ssz * Consistently move keymanager jsdoc to routes * Sanitize user provided init values before merging * Remove unused ssz only codec * Allow passing wire formats as string literals * chore: review proof routes (#6843) Review proof routes * chore: review lightclient routes (#6842) Review lightclient routes * chore: review node routes (#6844) Review node routes * feat: add cli flags to configure http wire format (#6840) * Review PR, mostly cosmetic changes * Fix event stream error handling --------- Co-authored-by: Cayman --- packages/api/README.md | 2 +- packages/api/package.json | 3 + packages/api/src/beacon/client/beacon.ts | 46 +- packages/api/src/beacon/client/config.ts | 13 +- packages/api/src/beacon/client/debug.ts | 58 +- packages/api/src/beacon/client/events.ts | 80 +- packages/api/src/beacon/client/index.ts | 16 +- packages/api/src/beacon/client/lightclient.ts | 13 +- packages/api/src/beacon/client/lodestar.ts | 13 +- packages/api/src/beacon/client/node.ts | 13 +- packages/api/src/beacon/client/proof.ts | 72 +- packages/api/src/beacon/client/validator.ts | 13 +- packages/api/src/beacon/index.ts | 10 +- .../api/src/beacon/routes/beacon/block.ts | 762 +++++---- .../api/src/beacon/routes/beacon/index.ts | 70 +- packages/api/src/beacon/routes/beacon/pool.ts | 377 +++-- .../api/src/beacon/routes/beacon/rewards.ts | 359 ++--- .../api/src/beacon/routes/beacon/state.ts | 759 ++++----- packages/api/src/beacon/routes/config.ts | 137 +- packages/api/src/beacon/routes/debug.ts | 291 ++-- packages/api/src/beacon/routes/events.ts | 100 +- packages/api/src/beacon/routes/index.ts | 39 +- packages/api/src/beacon/routes/lightclient.ts | 273 ++-- packages/api/src/beacon/routes/lodestar.ts | 424 +++-- packages/api/src/beacon/routes/node.ts | 299 ++-- packages/api/src/beacon/routes/proof.ts | 104 +- packages/api/src/beacon/routes/validator.ts | 1387 ++++++++++------- packages/api/src/beacon/server/beacon.ts | 66 +- packages/api/src/beacon/server/config.ts | 10 +- packages/api/src/beacon/server/debug.ts | 65 +- packages/api/src/beacon/server/events.ts | 42 +- packages/api/src/beacon/server/index.ts | 48 +- packages/api/src/beacon/server/lightclient.ts | 10 +- packages/api/src/beacon/server/lodestar.ts | 10 +- packages/api/src/beacon/server/node.ts | 30 +- packages/api/src/beacon/server/proof.ts | 47 +- packages/api/src/beacon/server/validator.ts | 32 +- packages/api/src/builder/client.ts | 16 +- packages/api/src/builder/index.ts | 22 +- packages/api/src/builder/routes.ts | 207 ++- packages/api/src/builder/server/index.ts | 27 +- packages/api/src/index.ts | 20 +- packages/api/src/interfaces.ts | 37 - packages/api/src/keymanager/client.ts | 13 +- packages/api/src/keymanager/index.ts | 23 +- packages/api/src/keymanager/routes.ts | 773 +++++---- packages/api/src/keymanager/server/index.ts | 27 +- packages/api/src/server/index.ts | 2 + packages/api/src/utils/acceptHeader.ts | 81 - packages/api/src/utils/client/client.ts | 125 -- packages/api/src/utils/client/error.ts | 10 + packages/api/src/utils/client/httpClient.ts | 493 +++--- packages/api/src/utils/client/index.ts | 8 +- packages/api/src/utils/client/method.ts | 50 + packages/api/src/utils/client/request.ts | 108 ++ packages/api/src/utils/client/response.ts | 201 +++ packages/api/src/utils/codecs.ts | 144 ++ packages/api/src/utils/fork.ts | 40 + packages/api/src/utils/headers.ts | 161 ++ .../src/utils/{client => }/httpStatusCode.ts | 0 packages/api/src/utils/index.ts | 2 +- packages/api/src/utils/metadata.ts | 164 ++ packages/api/src/utils/routes.ts | 39 - packages/api/src/utils/schema.ts | 55 +- packages/api/src/utils/serdes.ts | 18 + .../src/utils/server/{errors.ts => error.ts} | 2 +- .../api/src/utils/server/genericJsonServer.ts | 59 - packages/api/src/utils/server/handler.ts | 146 ++ packages/api/src/utils/server/index.ts | 9 +- packages/api/src/utils/server/method.ts | 39 + packages/api/src/utils/server/parser.ts | 27 + .../api/src/utils/server/registerRoute.ts | 17 - packages/api/src/utils/server/route.ts | 45 + packages/api/src/utils/server/types.ts | 33 - packages/api/src/utils/types.ts | 354 ++--- packages/api/src/utils/urlFormat.ts | 6 +- packages/api/src/utils/wireFormat.ts | 24 + .../test/perf/compileRouteUrlFormater.test.ts | 8 +- .../beacon/genericServerTest/beacon.test.ts | 4 +- .../beacon/genericServerTest/config.test.ts | 12 +- .../beacon/genericServerTest/debug.test.ts | 24 +- .../beacon/genericServerTest/events.test.ts | 26 +- .../genericServerTest/lightclient.test.ts | 4 +- .../beacon/genericServerTest/node.test.ts | 4 +- .../beacon/genericServerTest/proofs.test.ts | 4 +- .../genericServerTest/validator.test.ts | 4 +- .../api/test/unit/beacon/oapiSpec.test.ts | 70 +- .../api/test/unit/beacon/testData/beacon.ts | 145 +- .../api/test/unit/beacon/testData/config.ts | 10 +- .../api/test/unit/beacon/testData/debug.ts | 20 +- .../api/test/unit/beacon/testData/events.ts | 6 +- .../test/unit/beacon/testData/lightclient.ts | 36 +- .../api/test/unit/beacon/testData/node.ts | 18 +- .../api/test/unit/beacon/testData/proofs.ts | 19 +- .../test/unit/beacon/testData/validator.ts | 135 +- .../api/test/unit/builder/builder.test.ts | 4 +- .../api/test/unit/builder/oapiSpec.test.ts | 7 +- packages/api/test/unit/builder/testData.ts | 16 +- .../api/test/unit/client/httpClient.test.ts | 314 +++- .../unit/client/httpClientFallback.test.ts | 29 +- .../unit/client/httpClientOptions.test.ts | 57 +- .../api/test/unit/client/urlFormat.test.ts | 6 +- .../test/unit/keymanager/keymanager.test.ts | 4 +- .../api/test/unit/keymanager/oapiSpec.test.ts | 9 +- packages/api/test/unit/keymanager/testData.ts | 46 +- .../{acceptHeader.test.ts => headers.test.ts} | 44 +- packages/api/test/utils/checkAgainstSpec.ts | 86 +- packages/api/test/utils/genericServerTest.ts | 111 +- packages/api/test/utils/parseOpenApiSpec.ts | 103 +- packages/api/test/utils/utils.ts | 13 +- packages/beacon-node/src/api/impl/api.ts | 4 +- .../src/api/impl/beacon/blocks/index.ts | 145 +- .../beacon-node/src/api/impl/beacon/index.ts | 5 +- .../src/api/impl/beacon/pool/index.ts | 41 +- .../src/api/impl/beacon/rewards/index.ts | 19 +- .../src/api/impl/beacon/state/index.ts | 93 +- .../src/api/impl/beacon/state/utils.ts | 7 +- .../beacon-node/src/api/impl/config/index.ts | 5 +- .../beacon-node/src/api/impl/debug/index.ts | 35 +- packages/beacon-node/src/api/impl/errors.ts | 2 +- .../beacon-node/src/api/impl/events/index.ts | 9 +- .../src/api/impl/lightclient/index.ts | 42 +- .../src/api/impl/lodestar/index.ts | 38 +- .../beacon-node/src/api/impl/node/index.ts | 25 +- .../beacon-node/src/api/impl/proof/index.ts | 25 +- .../src/api/impl/validator/index.ts | 651 ++++---- packages/beacon-node/src/api/rest/base.ts | 3 + packages/beacon-node/src/api/rest/index.ts | 7 +- .../src/chain/beaconProposerCache.ts | 10 +- .../beacon-node/src/execution/builder/http.ts | 36 +- packages/beacon-node/src/node/nodejs.ts | 6 +- .../api/impl/beacon/node/endpoints.test.ts | 16 +- .../api/impl/beacon/state/endpoint.test.ts | 18 +- .../e2e/api/impl/lightclient/endpoint.test.ts | 44 +- .../test/e2e/api/lodestar/lodestar.test.ts | 38 +- .../test/e2e/chain/lightclient.test.ts | 16 +- .../test/scripts/blsPubkeyBytesFrequency.ts | 9 +- .../beacon-node/test/sim/mergemock.test.ts | 2 +- .../test/unit/api/impl/beacon/beacon.test.ts | 3 +- .../beacon/blocks/getBlockHeaders.test.ts | 9 +- .../test/unit/api/impl/config/config.test.ts | 5 +- .../test/unit/api/impl/events/events.test.ts | 8 +- .../impl/validator/duties/proposer.test.ts | 37 +- .../validator/produceAttestationData.test.ts | 6 +- .../api/impl/validator/produceBlockV2.test.ts | 12 +- .../api/impl/validator/produceBlockV3.test.ts | 12 +- .../test/unit/chain/beaconProposerCache.ts | 16 +- .../beacon-node/test/utils/node/validator.ts | 49 +- packages/cli/src/cmds/lightclient/handler.ts | 9 +- .../cmds/validator/blsToExecutionChange.ts | 26 +- packages/cli/src/cmds/validator/handler.ts | 26 +- .../cli/src/cmds/validator/keymanager/impl.ts | 241 ++- .../src/cmds/validator/keymanager/server.ts | 7 +- packages/cli/src/cmds/validator/options.ts | 18 + .../validator/slashingProtection/utils.ts | 6 +- .../cli/src/cmds/validator/voluntaryExit.ts | 27 +- packages/cli/src/networks/index.ts | 14 +- .../cli/test/e2e/blsToExecutionchange.test.ts | 14 +- .../test/e2e/importKeystoresFromApi.test.ts | 32 +- .../test/e2e/importRemoteKeysFromApi.test.ts | 34 +- .../e2e/propserConfigfromKeymanager.test.ts | 122 +- packages/cli/test/e2e/runDevCmd.test.ts | 6 +- packages/cli/test/e2e/voluntaryExit.test.ts | 16 +- .../cli/test/e2e/voluntaryExitFromApi.test.ts | 22 +- .../e2e/voluntaryExitRemoteSigner.test.ts | 14 +- packages/cli/test/sim/endpoints.test.ts | 45 +- .../crucible/assertions/blobsAssertion.ts | 6 +- .../attestationParticipationAssertion.ts | 6 +- .../defaults/connectedPeerCountAssertion.ts | 5 +- .../assertions/defaults/finalizedAssertion.ts | 6 +- .../assertions/defaults/headAssertion.ts | 8 +- .../assertions/executionHeadAssertion.ts | 8 +- .../crucible/assertions/forkAssertion.ts | 6 +- .../lighthousePeerScoreAssertion.ts | 10 +- .../crucible/assertions/mergeAssertion.ts | 6 +- .../crucible/assertions/nodeAssertion.ts | 6 +- .../assertions/withdrawalsAssertion.ts | 27 +- .../crucible/clients/beacon/lighthouse.ts | 6 +- .../cli/test/utils/crucible/interfaces.ts | 8 +- .../cli/test/utils/crucible/simulation.ts | 8 +- .../test/utils/crucible/simulationTracker.ts | 30 +- .../cli/test/utils/crucible/utils/network.ts | 31 +- .../cli/test/utils/crucible/utils/syncing.ts | 63 +- .../cli/test/utils/mockBeaconApiServer.ts | 16 +- packages/cli/test/utils/validator.ts | 18 +- packages/flare/src/cmds/selfSlashAttester.ts | 13 +- packages/flare/src/cmds/selfSlashProposer.ts | 13 +- packages/light-client/src/transport/rest.ts | 43 +- packages/light-client/src/utils/api.ts | 4 +- packages/light-client/src/utils/utils.ts | 17 +- .../test/mocks/EventsServerApiMock.ts | 15 +- .../test/mocks/LightclientServerApiMock.ts | 69 +- .../light-client/test/unit/sync.node.test.ts | 18 +- .../light-client/test/utils/getGenesisData.ts | 9 +- packages/light-client/test/utils/server.ts | 13 +- packages/prover/README.md | 2 +- .../src/proof_provider/payload_store.ts | 4 +- .../src/proof_provider/proof_provider.ts | 6 +- packages/prover/src/utils/consensus.ts | 37 +- .../unit/proof_provider/payload_store.test.ts | 45 +- packages/reqresp/README.md | 2 +- .../test/perf/analyzeBlocks.ts | 14 +- .../test/perf/analyzeEpochs.ts | 14 +- .../test/utils/testFileCache.ts | 35 +- packages/types/src/utils/stringType.ts | 3 + packages/validator/src/genesis.ts | 8 +- .../validator/src/services/attestation.ts | 38 +- .../src/services/attestationDuties.ts | 19 +- packages/validator/src/services/block.ts | 61 +- .../validator/src/services/blockDuties.ts | 13 +- .../src/services/chainHeaderTracker.ts | 16 +- .../src/services/doppelgangerService.ts | 14 +- packages/validator/src/services/indices.ts | 23 +- .../src/services/prepareBeaconProposer.ts | 12 +- .../validator/src/services/syncCommittee.ts | 26 +- .../src/services/syncCommitteeDuties.ts | 12 +- packages/validator/src/validator.ts | 65 +- .../test/unit/services/attestation.test.ts | 67 +- .../unit/services/attestationDuties.test.ts | 48 +- .../test/unit/services/block.test.ts | 96 +- .../test/unit/services/blockDuties.test.ts | 97 +- .../test/unit/services/doppelganger.test.ts | 33 +- .../unit/services/syncCommitteDuties.test.ts | 53 +- .../test/unit/services/syncCommittee.test.ts | 58 +- packages/validator/test/utils/apiStub.ts | 18 +- .../validator/test/utils/validatorStore.ts | 4 +- 226 files changed, 8009 insertions(+), 6727 deletions(-) delete mode 100644 packages/api/src/interfaces.ts create mode 100644 packages/api/src/server/index.ts delete mode 100644 packages/api/src/utils/acceptHeader.ts delete mode 100644 packages/api/src/utils/client/client.ts create mode 100644 packages/api/src/utils/client/error.ts create mode 100644 packages/api/src/utils/client/method.ts create mode 100644 packages/api/src/utils/client/request.ts create mode 100644 packages/api/src/utils/client/response.ts create mode 100644 packages/api/src/utils/codecs.ts create mode 100644 packages/api/src/utils/fork.ts create mode 100644 packages/api/src/utils/headers.ts rename packages/api/src/utils/{client => }/httpStatusCode.ts (100%) create mode 100644 packages/api/src/utils/metadata.ts delete mode 100644 packages/api/src/utils/routes.ts rename packages/api/src/utils/server/{errors.ts => error.ts} (76%) delete mode 100644 packages/api/src/utils/server/genericJsonServer.ts create mode 100644 packages/api/src/utils/server/handler.ts create mode 100644 packages/api/src/utils/server/method.ts create mode 100644 packages/api/src/utils/server/parser.ts delete mode 100644 packages/api/src/utils/server/registerRoute.ts create mode 100644 packages/api/src/utils/server/route.ts delete mode 100644 packages/api/src/utils/server/types.ts create mode 100644 packages/api/src/utils/wireFormat.ts rename packages/api/test/unit/utils/{acceptHeader.test.ts => headers.test.ts} (56%) diff --git a/packages/api/README.md b/packages/api/README.md index 39d7098d60ce..5a1178e9c766 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -1,7 +1,7 @@ # Lodestar Eth Consensus API [![Discord](https://img.shields.io/discord/593655374469660673.svg?label=Discord&logo=discord)](https://discord.gg/aMxzVcr) -[![ETH Beacon APIs Spec v2.1.0](https://img.shields.io/badge/ETH%20beacon--APIs-2.1.0-blue)](https://github.com/ethereum/beacon-APIs/releases/tag/v2.1.0) +[![ETH Beacon APIs Spec v2.5.0](https://img.shields.io/badge/ETH%20beacon--APIs-2.5.0-blue)](https://github.com/ethereum/beacon-APIs/releases/tag/v2.5.0) ![ES Version](https://img.shields.io/badge/ES-2021-yellow) ![Node Version](https://img.shields.io/badge/node-22.x-green) diff --git a/packages/api/package.json b/packages/api/package.json index ae147b5fda7c..67b966fcfb30 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -17,6 +17,9 @@ ".": { "import": "./lib/index.js" }, + "./server": { + "import": "./lib/server/index.js" + }, "./beacon": { "import": "./lib/beacon/index.js" }, diff --git a/packages/api/src/beacon/client/beacon.ts b/packages/api/src/beacon/client/beacon.ts index ef1e1983577d..803c2e44d6e4 100644 --- a/packages/api/src/beacon/client/beacon.ts +++ b/packages/api/src/beacon/client/beacon.ts @@ -1,46 +1,12 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes, BlockId} from "../routes/beacon/index.js"; -import {IHttpClient, generateGenericJsonClient, getFetchOptsSerializers} from "../../utils/client/index.js"; -import {ResponseFormat} from "../../interfaces.js"; -import {BlockResponse, BlockV2Response} from "../routes/beacon/block.js"; +import {ApiClientMethods, IHttpClient, createApiClientMethods} from "../../utils/client/index.js"; +import {Endpoints, getDefinitions} from "../routes/beacon/index.js"; + +export type ApiClient = ApiClientMethods; /** * REST HTTP client for beacon routes */ -export function getClient(config: ChainForkConfig, httpClient: IHttpClient): Api { - const reqSerializers = getReqSerializers(config); - const returnTypes = getReturnTypes(); - // Some routes return JSON, use a client auto-generator - const client = generateGenericJsonClient(routesData, reqSerializers, returnTypes, httpClient) as Api; - const fetchOptsSerializer = getFetchOptsSerializers(routesData, reqSerializers); - - return { - ...client, - async getBlock(blockId: BlockId, format?: T) { - if (format === "ssz") { - const res = await httpClient.arrayBuffer({ - ...fetchOptsSerializer.getBlock(blockId, format), - }); - return { - ok: true, - response: new Uint8Array(res.body), - status: res.status, - } as BlockResponse; - } - return client.getBlock(blockId, format); - }, - async getBlockV2(blockId: BlockId, format?: T) { - if (format === "ssz") { - const res = await httpClient.arrayBuffer({ - ...fetchOptsSerializer.getBlockV2(blockId, format), - }); - return { - ok: true, - response: new Uint8Array(res.body), - status: res.status, - } as BlockV2Response; - } - return client.getBlockV2(blockId, format); - }, - }; +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/beacon/client/config.ts b/packages/api/src/beacon/client/config.ts index b005410e1400..66cddbc52136 100644 --- a/packages/api/src/beacon/client/config.ts +++ b/packages/api/src/beacon/client/config.ts @@ -1,13 +1,12 @@ import {ChainForkConfig} from "@lodestar/config"; -import {generateGenericJsonClient, IHttpClient} from "../../utils/client/index.js"; -import {Api, getReqSerializers, getReturnTypes, ReqTypes, routesData} from "../routes/config.js"; +import {ApiClientMethods, IHttpClient, createApiClientMethods} from "../../utils/client/index.js"; +import {Endpoints, getDefinitions} from "../routes/config.js"; + +export type ApiClient = ApiClientMethods; /** * REST HTTP client for config routes */ -export function getClient(config: ChainForkConfig, httpClient: IHttpClient): Api { - const reqSerializers = getReqSerializers(); - const returnTypes = getReturnTypes(); - // All routes return JSON, use a client auto-generator - return generateGenericJsonClient(routesData, reqSerializers, returnTypes, httpClient); +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/beacon/client/debug.ts b/packages/api/src/beacon/client/debug.ts index b322f2b21403..4df3bef12cf8 100644 --- a/packages/api/src/beacon/client/debug.ts +++ b/packages/api/src/beacon/client/debug.ts @@ -1,60 +1,12 @@ import {ChainForkConfig} from "@lodestar/config"; -import {ApiClientResponse, ResponseFormat} from "../../interfaces.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; -import {generateGenericJsonClient, getFetchOptsSerializers, IHttpClient} from "../../utils/client/index.js"; -import {StateId} from "../routes/beacon/state.js"; -import {Api, getReqSerializers, getReturnTypes, ReqTypes, routesData} from "../routes/debug.js"; +import {ApiClientMethods, createApiClientMethods, IHttpClient} from "../../utils/client/index.js"; +import {Endpoints, getDefinitions} from "../routes/debug.js"; -// As Jul 2022, it takes up to 3 mins to download states so make this 5 mins for reservation -const GET_STATE_TIMEOUT_MS = 5 * 60 * 1000; +export type ApiClient = ApiClientMethods; /** * REST HTTP client for debug routes */ -export function getClient(_config: ChainForkConfig, httpClient: IHttpClient): Api { - const reqSerializers = getReqSerializers(); - const returnTypes = getReturnTypes(); - // Some routes return JSON, use a client auto-generator - const client = generateGenericJsonClient(routesData, reqSerializers, returnTypes, httpClient); - // For `getState()` generate request serializer - const fetchOptsSerializers = getFetchOptsSerializers(routesData, reqSerializers); - - return { - ...client, - - // TODO: Debug the type issue - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - async getState(stateId: string, format?: ResponseFormat) { - if (format === "ssz") { - const res = await httpClient.arrayBuffer({ - ...fetchOptsSerializers.getState(stateId, format), - timeoutMs: GET_STATE_TIMEOUT_MS, - }); - return { - ok: true, - response: new Uint8Array(res.body), - status: res.status, - } as ApiClientResponse<{[HttpStatusCode.OK]: Uint8Array}>; - } - return client.getState(stateId, format); - }, - - // TODO: Debug the type issue - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - async getStateV2(stateId: StateId, format?: ResponseFormat) { - if (format === "ssz") { - const res = await httpClient.arrayBuffer({ - ...fetchOptsSerializers.getStateV2(stateId, format), - timeoutMs: GET_STATE_TIMEOUT_MS, - }); - return {ok: true, response: new Uint8Array(res.body), status: res.status} as ApiClientResponse<{ - [HttpStatusCode.OK]: Uint8Array; - }>; - } - - return client.getStateV2(stateId, format); - }, - }; +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/beacon/client/events.ts b/packages/api/src/beacon/client/events.ts index 1517d4bf3a56..97e2fdc40d75 100644 --- a/packages/api/src/beacon/client/events.ts +++ b/packages/api/src/beacon/client/events.ts @@ -1,59 +1,63 @@ -import {Api, BeaconEvent, routesData, getEventSerdes} from "../routes/events.js"; -import {stringifyQuery, urlJoin} from "../../utils/client/format.js"; +import {ChainForkConfig} from "@lodestar/config"; import {getEventSource} from "../../utils/client/eventSource.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; +import {stringifyQuery, urlJoin} from "../../utils/client/format.js"; +import {ApiClientMethods} from "../../utils/client/method.js"; +import {RouteDefinitionExtra} from "../../utils/client/request.js"; +import {ApiResponse} from "../../utils/client/response.js"; +import {BeaconEvent, Endpoints, getDefinitions, getEventSerdes} from "../routes/events.js"; + +export type ApiClient = ApiClientMethods; /** * REST HTTP client for events routes */ -export function getClient(baseUrl: string): Api { +export function getClient(config: ChainForkConfig, baseUrl: string): ApiClient { + const definitions = getDefinitions(config); const eventSerdes = getEventSerdes(); return { - eventstream: async (topics, signal, onEvent) => { + eventstream: async ({topics, signal, onEvent, onError, onClose}) => { const query = stringifyQuery({topics}); - const url = `${urlJoin(baseUrl, routesData.eventstream.url)}?${query}`; + const url = `${urlJoin(baseUrl, definitions.eventstream.url)}?${query}`; // eslint-disable-next-line @typescript-eslint/naming-convention const EventSource = await getEventSource(); const eventSource = new EventSource(url); - try { - await new Promise((resolve, reject) => { - for (const topic of topics) { - eventSource.addEventListener(topic, ((event: MessageEvent) => { - const message = eventSerdes.fromJson(topic, JSON.parse(event.data)); - onEvent({type: topic, message} as BeaconEvent); - }) as EventListener); - } - - // EventSource will try to reconnect always on all errors - // `eventSource.onerror` events are informative but don't indicate the EventSource closed - // The only way to abort the connection from the client is via eventSource.close() - eventSource.onerror = function onerror(err) { - const errEs = err as unknown as EventSourceError; - // Consider 400 and 500 status errors unrecoverable, close the eventsource - if (errEs.status === 400) { - reject(Error(`400 Invalid topics: ${errEs.message}`)); - } - if (errEs.status === 500) { - reject(Error(`500 Internal Server Error: ${errEs.message}`)); - } - - // TODO: else log the error somewhere - // console.log("eventstream client error", errEs); - }; - - // And abort resolve the promise so finally {} eventSource.close() - signal.addEventListener("abort", () => resolve(), {once: true}); - }); - } finally { + const close = (): void => { eventSource.close(); + onClose?.(); + signal.removeEventListener("abort", close); + }; + signal.addEventListener("abort", close, {once: true}); + + for (const topic of topics) { + eventSource.addEventListener(topic, (event: MessageEvent) => { + const message = eventSerdes.fromJson(topic, JSON.parse(event.data)); + onEvent({type: topic, message} as BeaconEvent); + }); } - return {ok: true, response: undefined, status: HttpStatusCode.OK}; + // EventSource will try to reconnect always on all errors + // `eventSource.onerror` events are informative but don't indicate the EventSource closed + // The only way to abort the connection from the client is via eventSource.close() + eventSource.onerror = function onerror(err) { + const errEs = err as unknown as EventSourceError; + + // Ignore noisy errors due to beacon node being offline + if (!errEs.message?.includes("ECONNREFUSED")) { + onError?.(new Error(errEs.message)); + } + + // Consider 400 and 500 status errors unrecoverable, close the eventsource + if (errEs.status === 400 || errEs.status === 500) { + close(); + } + }; + + return new ApiResponse(definitions.eventstream as RouteDefinitionExtra); }, }; } // https://github.com/EventSource/eventsource/blob/82e034389bd2c08d532c63172b8e858c5b185338/lib/eventsource.js#L143 -type EventSourceError = {status?: number; message: string}; +type EventSourceError = {status?: number; message?: string}; diff --git a/packages/api/src/beacon/client/index.ts b/packages/api/src/beacon/client/index.ts index 9fbe17bf337a..6512d23673c5 100644 --- a/packages/api/src/beacon/client/index.ts +++ b/packages/api/src/beacon/client/index.ts @@ -1,6 +1,12 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api} from "../routes/index.js"; -import {IHttpClient, HttpClient, HttpClientOptions, HttpClientModules} from "../../utils/client/index.js"; +import { + ApiClientMethods, + HttpClient, + HttpClientModules, + HttpClientOptions, + IHttpClient, +} from "../../utils/client/index.js"; +import {Endpoints} from "../routes/index.js"; import * as beacon from "./beacon.js"; import * as configApi from "./config.js"; @@ -17,10 +23,12 @@ type ClientModules = HttpClientModules & { httpClient?: IHttpClient; }; +export type ApiClient = {[K in keyof Endpoints]: ApiClientMethods}; + /** * REST HTTP client for all routes */ -export function getClient(opts: HttpClientOptions, modules: ClientModules): Api { +export function getClient(opts: HttpClientOptions, modules: ClientModules): ApiClient { const {config} = modules; const httpClient = modules.httpClient ?? new HttpClient(opts, modules); @@ -28,7 +36,7 @@ export function getClient(opts: HttpClientOptions, modules: ClientModules): Api beacon: beacon.getClient(config, httpClient), config: configApi.getClient(config, httpClient), debug: debug.getClient(config, httpClient), - events: events.getClient(httpClient.baseUrl), + events: events.getClient(config, httpClient.baseUrl), lightclient: lightclient.getClient(config, httpClient), lodestar: lodestar.getClient(config, httpClient), node: node.getClient(config, httpClient), diff --git a/packages/api/src/beacon/client/lightclient.ts b/packages/api/src/beacon/client/lightclient.ts index 44092757629f..dbb4f3047de0 100644 --- a/packages/api/src/beacon/client/lightclient.ts +++ b/packages/api/src/beacon/client/lightclient.ts @@ -1,13 +1,12 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "../routes/lightclient.js"; -import {IHttpClient, generateGenericJsonClient} from "../../utils/client/index.js"; +import {ApiClientMethods, IHttpClient, createApiClientMethods} from "../../utils/client/index.js"; +import {Endpoints, getDefinitions} from "../routes/lightclient.js"; + +export type ApiClient = ApiClientMethods; /** * REST HTTP client for lightclient routes */ -export function getClient(_config: ChainForkConfig, httpClient: IHttpClient): Api { - const reqSerializers = getReqSerializers(); - const returnTypes = getReturnTypes(); - // All routes return JSON, use a client auto-generator - return generateGenericJsonClient(routesData, reqSerializers, returnTypes, httpClient); +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/beacon/client/lodestar.ts b/packages/api/src/beacon/client/lodestar.ts index 9f6dfa305ad5..a2aeb2224acd 100644 --- a/packages/api/src/beacon/client/lodestar.ts +++ b/packages/api/src/beacon/client/lodestar.ts @@ -1,13 +1,12 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "../routes/lodestar.js"; -import {IHttpClient, generateGenericJsonClient} from "../../utils/client/index.js"; +import {ApiClientMethods, IHttpClient, createApiClientMethods} from "../../utils/client/index.js"; +import {Endpoints, getDefinitions} from "../routes/lodestar.js"; + +export type ApiClient = ApiClientMethods; /** * REST HTTP client for lodestar routes */ -export function getClient(_config: ChainForkConfig, httpClient: IHttpClient): Api { - const reqSerializers = getReqSerializers(); - const returnTypes = getReturnTypes(); - // All routes return JSON, use a client auto-generator - return generateGenericJsonClient(routesData, reqSerializers, returnTypes, httpClient); +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/beacon/client/node.ts b/packages/api/src/beacon/client/node.ts index adea7b1b5420..8f6599e9ec40 100644 --- a/packages/api/src/beacon/client/node.ts +++ b/packages/api/src/beacon/client/node.ts @@ -1,13 +1,12 @@ import {ChainForkConfig} from "@lodestar/config"; -import {generateGenericJsonClient, IHttpClient} from "../../utils/client/index.js"; -import {Api, getReqSerializers, getReturnTypes, ReqTypes, routesData} from "../routes/node.js"; +import {ApiClientMethods, IHttpClient, createApiClientMethods} from "../../utils/client/index.js"; +import {Endpoints, getDefinitions} from "../routes/node.js"; + +export type ApiClient = ApiClientMethods; /** * REST HTTP client for beacon routes */ -export function getClient(_config: ChainForkConfig, httpClient: IHttpClient): Api { - const reqSerializers = getReqSerializers(); - const returnTypes = getReturnTypes(); - // All routes return JSON, use a client auto-generator - return generateGenericJsonClient(routesData, reqSerializers, returnTypes, httpClient); +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/beacon/client/proof.ts b/packages/api/src/beacon/client/proof.ts index 5188725da148..504afe324f3e 100644 --- a/packages/api/src/beacon/client/proof.ts +++ b/packages/api/src/beacon/client/proof.ts @@ -1,70 +1,12 @@ -import {CompactMultiProof, ProofType} from "@chainsafe/persistent-merkle-tree"; import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReqSerializers} from "../routes/proof.js"; -import {IHttpClient, getFetchOptsSerializers, HttpError} from "../../utils/client/index.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; +import {ApiClientMethods, IHttpClient, createApiClientMethods} from "../../utils/client/index.js"; +import {Endpoints, getDefinitions} from "../routes/proof.js"; + +export type ApiClient = ApiClientMethods; /** - * REST HTTP client for lightclient routes + * REST HTTP client for proof routes */ -export function getClient(_config: ChainForkConfig, httpClient: IHttpClient): Api { - const reqSerializers = getReqSerializers(); - - // For `getStateProof()` generate request serializer - const fetchOptsSerializers = getFetchOptsSerializers(routesData, reqSerializers); - - return { - async getStateProof(stateId, descriptor) { - try { - const res = await httpClient.arrayBuffer(fetchOptsSerializers.getStateProof(stateId, descriptor)); - // reuse the response ArrayBuffer - if (!Number.isInteger(res.body.byteLength / 32)) { - throw new Error("Invalid proof data: Length not divisible by 32"); - } - - const proof: CompactMultiProof = { - type: ProofType.compactMulti, - descriptor, - leaves: Array.from({length: res.body.byteLength / 32}, (_, i) => new Uint8Array(res.body, i * 32, 32)), - }; - - return {ok: true, response: {data: proof}, status: HttpStatusCode.OK}; - } catch (err) { - if (err instanceof HttpError) { - return { - ok: false, - error: {code: err.status, message: err.message, operationId: "proof.getStateProof"}, - status: err.status, - }; - } - throw err; - } - }, - async getBlockProof(blockId, descriptor) { - try { - const res = await httpClient.arrayBuffer(fetchOptsSerializers.getBlockProof(blockId, descriptor)); - // reuse the response ArrayBuffer - if (!Number.isInteger(res.body.byteLength / 32)) { - throw new Error("Invalid proof data: Length not divisible by 32"); - } - - const proof: CompactMultiProof = { - type: ProofType.compactMulti, - descriptor, - leaves: Array.from({length: res.body.byteLength / 32}, (_, i) => new Uint8Array(res.body, i * 32, 32)), - }; - - return {ok: true, response: {data: proof}, status: HttpStatusCode.OK}; - } catch (err) { - if (err instanceof HttpError) { - return { - ok: false, - error: {code: err.status, message: err.message, operationId: "proof.getStateProof"}, - status: err.status, - }; - } - throw err; - } - }, - }; +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/beacon/client/validator.ts b/packages/api/src/beacon/client/validator.ts index d93c4ac6ed58..79c56c6da590 100644 --- a/packages/api/src/beacon/client/validator.ts +++ b/packages/api/src/beacon/client/validator.ts @@ -1,13 +1,12 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "../routes/validator.js"; -import {IHttpClient, generateGenericJsonClient} from "../../utils/client/index.js"; +import {ApiClientMethods, IHttpClient, createApiClientMethods} from "../../utils/client/index.js"; +import {Endpoints, getDefinitions} from "../routes/validator.js"; + +export type ApiClient = ApiClientMethods; /** * REST HTTP client for validator routes */ -export function getClient(_config: ChainForkConfig, httpClient: IHttpClient): Api { - const reqSerializers = getReqSerializers(); - const returnTypes = getReturnTypes(); - // All routes return JSON, use a client auto-generator - return generateGenericJsonClient(routesData, reqSerializers, returnTypes, httpClient); +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/beacon/index.ts b/packages/api/src/beacon/index.ts index 7f0e10536d4d..660f2241db2a 100644 --- a/packages/api/src/beacon/index.ts +++ b/packages/api/src/beacon/index.ts @@ -1,15 +1,15 @@ -import type {Api} from "./routes/index.js"; +import type {Endpoints} from "./routes/index.js"; // NOTE: Don't export server here so it's not bundled to all consumers import * as routes from "./routes/index.js"; export {routes}; -export {getClient} from "./client/index.js"; -export type {Api}; +export {getClient, type ApiClient} from "./client/index.js"; +export type {Endpoints}; // Declare namespaces for CLI options -export type ApiNamespace = keyof Api; -const allNamespacesObj: {[K in keyof Api]: true} = { +export type ApiNamespace = keyof Endpoints; +const allNamespacesObj: {[K in keyof Endpoints]: true} = { beacon: true, config: true, debug: true, diff --git a/packages/api/src/beacon/routes/beacon/block.ts b/packages/api/src/beacon/routes/beacon/block.ts index 7dd5511a22f6..380efcc9825a 100644 --- a/packages/api/src/beacon/routes/beacon/block.ts +++ b/packages/api/src/beacon/routes/beacon/block.ts @@ -1,51 +1,58 @@ -import {ContainerType} from "@chainsafe/ssz"; -import {ForkName} from "@lodestar/params"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {ContainerType, ListCompositeType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {phase0, allForks, Slot, Root, ssz, RootHex, deneb, isSignedBlockContents} from "@lodestar/types"; - +import {allForks, Slot, ssz, RootHex, deneb, phase0, isSignedBlockContents} from "@lodestar/types"; +import {ForkName, ForkSeq} from "@lodestar/params"; +import {Endpoint, RequestCodec, RouteDefinitions, Schema} from "../../../utils/index.js"; +import {EmptyMeta, EmptyMetaCodec, EmptyResponseCodec, EmptyResponseData, WithVersion} from "../../../utils/codecs.js"; import { - RoutesData, - ReturnTypes, - ArrayOf, - Schema, - WithVersion, - reqOnlyBody, - TypeJson, - ReqSerializers, - ReqSerializer, - ContainerDataExecutionOptimistic, - WithExecutionOptimistic, - ContainerData, - WithFinalized, -} from "../../../utils/index.js"; -import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; -import {parseAcceptHeader, writeAcceptHeader} from "../../../utils/acceptHeader.js"; -import {ApiClientResponse, ResponseFormat} from "../../../interfaces.js"; -import {allForksSignedBlockContentsReqSerializer} from "../../../utils/routes.js"; + ExecutionOptimisticAndFinalizedCodec, + ExecutionOptimisticAndFinalizedMeta, + ExecutionOptimisticFinalizedAndVersionCodec, + ExecutionOptimisticFinalizedAndVersionMeta, + MetaHeader, +} from "../../../utils/metadata.js"; +import {getBlindedForkTypes, toForkName} from "../../../utils/fork.js"; +import {fromHeaders} from "../../../utils/headers.js"; +import {WireFormat} from "../../../utils/wireFormat.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -export type BlockId = RootHex | Slot | "head" | "genesis" | "finalized"; - -/** - * True if the response references an unverified execution payload. Optimistic information may be invalidated at - * a later time. If the field is not present, assume the False value. - */ -export type ExecutionOptimistic = boolean; - -/** - * True if the response references the finalized history of the chain, as determined by fork choice. - */ -export type Finalized = boolean; - -export type BlockHeaderResponse = { - root: Root; - canonical: boolean; - header: phase0.SignedBeaconBlockHeader; +export const BlockHeaderResponseType = new ContainerType({ + root: ssz.Root, + canonical: ssz.Boolean, + header: ssz.phase0.SignedBeaconBlockHeader, +}); +export const BlockHeadersResponseType = new ListCompositeType(BlockHeaderResponseType, 1000); +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"; + +export type BlockArgs = { + /** + * Block identifier. + * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. + */ + blockId: BlockId; }; export enum BroadcastValidation { - /* + /** NOTE: The value `none` is not part of the spec. In case a node is configured only with the unknownBlockSync, it needs to know the unknown parent blocks on the network @@ -58,120 +65,78 @@ export enum BroadcastValidation { consensusAndEquivocation = "consensus_and_equivocation", } -export type BlockResponse = T extends "ssz" - ? ApiClientResponse<{[HttpStatusCode.OK]: Uint8Array}, HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND> - : ApiClientResponse< - {[HttpStatusCode.OK]: {data: allForks.SignedBeaconBlock}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - >; - -export type BlockV2Response = T extends "ssz" - ? ApiClientResponse<{[HttpStatusCode.OK]: Uint8Array}, HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND> - : ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: allForks.SignedBeaconBlock; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - version: ForkName; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - >; - -export type Api = { +export type Endpoints = { /** * Get block * Returns the complete `SignedBeaconBlock` for a given block ID. - * Depending on the `Accept` header it can be returned either as JSON or SSZ-serialized bytes. - * - * @param blockId Block identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. */ - getBlock(blockId: BlockId, format?: T): Promise>; + getBlock: Endpoint< + // ⏎ + "GET", + BlockArgs, + {params: {block_id: string}}, + phase0.SignedBeaconBlock, + EmptyMeta + >; /** * Get block * Retrieves block details for given block id. - * @param blockId Block identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. */ - getBlockV2(blockId: BlockId, format?: T): Promise>; + getBlockV2: Endpoint< + "GET", + BlockArgs, + {params: {block_id: string}}, + allForks.SignedBeaconBlock, + ExecutionOptimisticFinalizedAndVersionMeta + >; /** * Get block attestations * Retrieves attestation included in requested block. - * @param blockId Block identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. */ - getBlockAttestations(blockId: BlockId): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: phase0.Attestation[]; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getBlockAttestations: Endpoint< + "GET", + BlockArgs, + {params: {block_id: string}}, + allForks.BeaconBlockBody["attestations"], + ExecutionOptimisticAndFinalizedMeta >; /** * Get block header * Retrieves block header for given block id. - * @param blockId Block identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. */ - getBlockHeader(blockId: BlockId): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: BlockHeaderResponse; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getBlockHeader: Endpoint< + "GET", + BlockArgs, + {params: {block_id: string}}, + BlockHeaderResponse, + ExecutionOptimisticAndFinalizedMeta >; /** * Get block headers * Retrieves block headers matching given query. By default it will fetch current head slot blocks. - * @param slot - * @param parentRoot */ - getBlockHeaders(filters: Partial<{slot: Slot; parentRoot: string}>): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: BlockHeaderResponse[]; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST - > + getBlockHeaders: Endpoint< + "GET", + {slot?: Slot; parentRoot?: string}, + {query: {slot?: number; parent_root?: string}}, + BlockHeaderResponse[], + ExecutionOptimisticAndFinalizedMeta >; /** * Get block root * Retrieves hashTreeRoot of BeaconBlock/BeaconBlockHeader - * @param blockId Block identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. */ - getBlockRoot(blockId: BlockId): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: {root: Root}; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getBlockRoot: Endpoint< + "GET", + BlockArgs, + {params: {block_id: string}}, + RootResponse, + ExecutionOptimisticAndFinalizedMeta >; /** @@ -183,238 +148,391 @@ export type Api = { * therefore validate the block internally, however blocks which fail the validation are still * broadcast but a different status code is returned (202) * - * @param requestBody The `SignedBeaconBlock` object composed of `BeaconBlock` object (produced by beacon node) and validator signature. - * @returns any The block was validated successfully and has been broadcast. It has also been integrated into the beacon node's database. + * Returns if the block was validated successfully and has been broadcast. It has also been integrated into the beacon node's database. */ - publishBlock(blockOrContents: allForks.SignedBeaconBlockOrContents): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: void; - [HttpStatusCode.ACCEPTED]: void; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + publishBlock: Endpoint< + "POST", + {signedBlockOrContents: allForks.SignedBeaconBlockOrContents}, + {body: unknown; headers: {[MetaHeader.Version]: string}}, + EmptyResponseData, + EmptyMeta >; - publishBlockV2( - blockOrContents: allForks.SignedBeaconBlockOrContents, - opts?: {broadcastValidation?: BroadcastValidation} - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: void; - [HttpStatusCode.ACCEPTED]: void; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + publishBlockV2: Endpoint< + "POST", + { + signedBlockOrContents: allForks.SignedBeaconBlockOrContents; + broadcastValidation?: BroadcastValidation; + }, + {body: unknown; headers: {[MetaHeader.Version]: string}; query: {broadcast_validation?: string}}, + EmptyResponseData, + EmptyMeta >; /** * Publish a signed blinded block by submitting it to the mev relay and patching in the block * transactions beacon node gets in response. */ - publishBlindedBlock(blindedBlock: allForks.SignedBlindedBeaconBlock): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: void; - [HttpStatusCode.ACCEPTED]: void; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + publishBlindedBlock: Endpoint< + "POST", + {signedBlindedBlock: allForks.SignedBlindedBeaconBlock}, + {body: unknown; headers: {[MetaHeader.Version]: string}}, + EmptyResponseData, + EmptyMeta >; - publishBlindedBlockV2( - blindedBlockOrContents: allForks.SignedBlindedBeaconBlock, - opts: {broadcastValidation?: BroadcastValidation} - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: void; - [HttpStatusCode.ACCEPTED]: void; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + publishBlindedBlockV2: Endpoint< + "POST", + { + signedBlindedBlock: allForks.SignedBlindedBeaconBlock; + broadcastValidation?: BroadcastValidation; + }, + {body: unknown; headers: {[MetaHeader.Version]: string}; query: {broadcast_validation?: string}}, + EmptyResponseData, + EmptyMeta >; + /** * Get block BlobSidecar * Retrieves BlobSidecar included in requested block. - * @param blockId Block identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. - * @param indices Array of indices for blob sidecars to request for in the specified block. Returns all blob sidecars in the block if not specified. */ - getBlobSidecars( - blockId: BlockId, - indices?: number[] - ): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: { - data: deneb.BlobSidecars; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }> + getBlobSidecars: Endpoint< + "GET", + BlockArgs & { + /** + * Array of indices for blob sidecars to request for in the specified block. + * Returns all blob sidecars in the block if not specified. + */ + indices?: number[]; + }, + {params: {block_id: string}; query: {indices?: number[]}}, + deneb.BlobSidecars, + ExecutionOptimisticFinalizedAndVersionMeta >; }; -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - getBlock: {url: "/eth/v1/beacon/blocks/{block_id}", method: "GET"}, - getBlockV2: {url: "/eth/v2/beacon/blocks/{block_id}", method: "GET"}, - getBlockAttestations: {url: "/eth/v1/beacon/blocks/{block_id}/attestations", method: "GET"}, - getBlockHeader: {url: "/eth/v1/beacon/headers/{block_id}", method: "GET"}, - getBlockHeaders: {url: "/eth/v1/beacon/headers", method: "GET"}, - getBlockRoot: {url: "/eth/v1/beacon/blocks/{block_id}/root", method: "GET"}, - publishBlock: {url: "/eth/v1/beacon/blocks", method: "POST"}, - publishBlockV2: {url: "/eth/v2/beacon/blocks", method: "POST"}, - publishBlindedBlock: {url: "/eth/v1/beacon/blinded_blocks", method: "POST"}, - publishBlindedBlockV2: {url: "/eth/v2/beacon/blinded_blocks", method: "POST"}, - getBlobSidecars: {url: "/eth/v1/beacon/blob_sidecars/{block_id}", method: "GET"}, +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const blockIdOnlyReq: RequestCodec> = { + writeReq: ({blockId}) => ({params: {block_id: blockId.toString()}}), + parseReq: ({params}) => ({blockId: params.block_id}), + schema: {params: {block_id: Schema.StringRequired}}, }; -/* eslint-disable @typescript-eslint/naming-convention */ - -type GetBlockReq = {params: {block_id: string}; headers: {accept?: string}}; -type BlockIdOnlyReq = {params: {block_id: string}}; - -export type ReqTypes = { - getBlock: GetBlockReq; - getBlockV2: GetBlockReq; - getBlockAttestations: BlockIdOnlyReq; - getBlockHeader: BlockIdOnlyReq; - getBlockHeaders: {query: {slot?: number; parent_root?: string}}; - getBlockRoot: BlockIdOnlyReq; - publishBlock: {body: unknown}; - publishBlockV2: {body: unknown; query: {broadcast_validation?: string}}; - publishBlindedBlock: {body: unknown}; - publishBlindedBlockV2: {body: unknown; query: {broadcast_validation?: string}}; - getBlobSidecars: {params: {block_id: string}; query: {indices?: number[]}}; -}; - -export function getReqSerializers(config: ChainForkConfig): ReqSerializers { - const blockIdOnlyReq: ReqSerializer = { - writeReq: (block_id) => ({params: {block_id: String(block_id)}}), - parseReq: ({params}) => [params.block_id], - schema: {params: {block_id: Schema.StringRequired}}, - }; - - const getBlockReq: ReqSerializer = { - writeReq: (block_id, format) => ({ - params: {block_id: String(block_id)}, - headers: {accept: writeAcceptHeader(format)}, - }), - parseReq: ({params, headers}) => [params.block_id, parseAcceptHeader(headers.accept)], - schema: {params: {block_id: Schema.StringRequired}}, - }; - - // Compute block type from JSON payload. See https://github.com/ethereum/eth2.0-APIs/pull/142 - const getSignedBeaconBlockType = (data: allForks.SignedBeaconBlock): allForks.AllForksSSZTypes["SignedBeaconBlock"] => - config.getForkTypes(data.message.slot).SignedBeaconBlock; - - const AllForksSignedBlockOrContents: TypeJson = { - toJson: (data) => - isSignedBlockContents(data) - ? allForksSignedBlockContentsReqSerializer(getSignedBeaconBlockType).toJson(data) - : getSignedBeaconBlockType(data).toJson(data), - - fromJson: (data) => - (data as {signed_block: unknown}).signed_block !== undefined - ? allForksSignedBlockContentsReqSerializer(getSignedBeaconBlockType).fromJson(data) - : getSignedBeaconBlockType(data as allForks.SignedBeaconBlock).fromJson(data), - }; - - const getSignedBlindedBeaconBlockType = ( - data: allForks.SignedBlindedBeaconBlock - ): allForks.AllForksBlindedSSZTypes["SignedBeaconBlock"] => - config.getBlindedForkTypes(data.message.slot).SignedBeaconBlock; - - const AllForksSignedBlindedBlock: TypeJson = { - toJson: (data) => getSignedBlindedBeaconBlockType(data).toJson(data), - fromJson: (data) => getSignedBlindedBeaconBlockType(data as allForks.SignedBlindedBeaconBlock).fromJson(data), - }; - - function extractSlot(signedBlockOrContents: allForks.SignedBeaconBlockOrContents): Slot { - return isSignedBlockContents(signedBlockOrContents) - ? signedBlockOrContents.signedBlock.message.slot - : signedBlockOrContents.message.slot; - } - +export function getDefinitions(config: ChainForkConfig): RouteDefinitions { return { - getBlock: getBlockReq, - getBlockV2: getBlockReq, - getBlockAttestations: blockIdOnlyReq, - getBlockHeader: blockIdOnlyReq, + getBlock: { + url: "/eth/v1/beacon/blocks/{block_id}", + method: "GET", + req: blockIdOnlyReq, + resp: { + data: ssz.phase0.SignedBeaconBlock, + meta: EmptyMetaCodec, + }, + }, + getBlockV2: { + url: "/eth/v2/beacon/blocks/{block_id}", + method: "GET", + req: blockIdOnlyReq, + resp: { + data: WithVersion((fork) => ssz[fork].SignedBeaconBlock), + meta: ExecutionOptimisticFinalizedAndVersionCodec, + }, + }, + getBlockAttestations: { + url: "/eth/v1/beacon/blocks/{block_id}/attestations", + method: "GET", + req: blockIdOnlyReq, + resp: { + data: ssz.phase0.BeaconBlockBody.fields.attestations, + meta: ExecutionOptimisticAndFinalizedCodec, + }, + }, + getBlockHeader: { + url: "/eth/v1/beacon/headers/{block_id}", + method: "GET", + req: blockIdOnlyReq, + resp: { + data: BlockHeaderResponseType, + meta: ExecutionOptimisticAndFinalizedCodec, + }, + }, getBlockHeaders: { - writeReq: (filters) => ({query: {slot: filters?.slot, parent_root: filters?.parentRoot}}), - parseReq: ({query}) => [{slot: query?.slot, parentRoot: query?.parent_root}], - schema: {query: {slot: Schema.Uint, parent_root: Schema.String}}, + url: "/eth/v1/beacon/headers", + method: "GET", + req: { + writeReq: ({slot, parentRoot}) => ({query: {slot, parent_root: parentRoot}}), + parseReq: ({query}) => ({slot: query.slot, parentRoot: query.parent_root}), + schema: {query: {slot: Schema.Uint, parent_root: Schema.String}}, + }, + resp: { + data: BlockHeadersResponseType, + meta: ExecutionOptimisticAndFinalizedCodec, + }, + }, + getBlockRoot: { + url: "/eth/v1/beacon/blocks/{block_id}/root", + method: "GET", + req: blockIdOnlyReq, + resp: { + data: RootResponseType, + meta: ExecutionOptimisticAndFinalizedCodec, + }, + }, + publishBlock: { + url: "/eth/v1/beacon/blocks", + method: "POST", + req: { + writeReqJson: ({signedBlockOrContents}) => { + const slot = isSignedBlockContents(signedBlockOrContents) + ? signedBlockOrContents.signedBlock.message.slot + : signedBlockOrContents.message.slot; + return { + body: + config.getForkSeq(slot) < ForkSeq.deneb + ? config + .getForkTypes(slot) + .SignedBeaconBlock.toJson(signedBlockOrContents as allForks.SignedBeaconBlock) + : SignedBlockContentsType.toJson(signedBlockOrContents as SignedBlockContents), + headers: { + [MetaHeader.Version]: config.getForkName(slot), + }, + }; + }, + parseReqJson: ({body, headers}) => { + let forkName: ForkName; + // As per spec, version header is optional for JSON requests + const versionHeader = fromHeaders(headers, MetaHeader.Version, false); + if (versionHeader !== undefined) { + forkName = toForkName(versionHeader); + } else { + // Determine fork from slot in JSON payload + forkName = config.getForkName( + (body as {signed_block: unknown}).signed_block !== undefined + ? (body as {signed_block: allForks.SignedBeaconBlock}).signed_block.message.slot + : (body as allForks.SignedBeaconBlock).message.slot + ); + } + const forkSeq = config.forks[forkName].seq; + return { + signedBlockOrContents: + forkSeq < ForkSeq.deneb + ? ssz[forkName].SignedBeaconBlock.fromJson(body) + : SignedBlockContentsType.fromJson(body), + }; + }, + writeReqSsz: ({signedBlockOrContents}) => { + const slot = isSignedBlockContents(signedBlockOrContents) + ? signedBlockOrContents.signedBlock.message.slot + : signedBlockOrContents.message.slot; + return { + body: + config.getForkSeq(slot) < ForkSeq.deneb + ? config + .getForkTypes(slot) + .SignedBeaconBlock.serialize(signedBlockOrContents as allForks.SignedBeaconBlock) + : SignedBlockContentsType.serialize(signedBlockOrContents as SignedBlockContents), + headers: { + [MetaHeader.Version]: config.getForkName(slot), + }, + }; + }, + parseReqSsz: ({body, headers}) => { + 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), + }; + }, + schema: { + body: Schema.Object, + headers: {[MetaHeader.Version]: Schema.String}, + }, + }, + resp: EmptyResponseCodec, + init: { + requestWireFormat: WireFormat.ssz, + }, }, - getBlockRoot: blockIdOnlyReq, - publishBlock: reqOnlyBody(AllForksSignedBlockOrContents, Schema.Object), publishBlockV2: { - writeReq: (item, {broadcastValidation} = {}) => ({ - body: AllForksSignedBlockOrContents.toJson(item), - query: {broadcast_validation: broadcastValidation}, - headers: {"Eth-Consensus-Version": config.getForkName(extractSlot(item))}, - }), - parseReq: ({body, query}) => [ - AllForksSignedBlockOrContents.fromJson(body), - {broadcastValidation: query.broadcast_validation as BroadcastValidation}, - ], - schema: { - body: Schema.Object, - query: {broadcast_validation: Schema.String}, + url: "/eth/v2/beacon/blocks", + method: "POST", + req: { + writeReqJson: ({signedBlockOrContents, broadcastValidation}) => { + const slot = isSignedBlockContents(signedBlockOrContents) + ? signedBlockOrContents.signedBlock.message.slot + : signedBlockOrContents.message.slot; + return { + body: + config.getForkSeq(slot) < ForkSeq.deneb + ? config + .getForkTypes(slot) + .SignedBeaconBlock.toJson(signedBlockOrContents as allForks.SignedBeaconBlock) + : SignedBlockContentsType.toJson(signedBlockOrContents as SignedBlockContents), + headers: { + [MetaHeader.Version]: config.getForkName(slot), + }, + query: {broadcast_validation: broadcastValidation}, + }; + }, + parseReqJson: ({body, headers, query}) => { + 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), + broadcastValidation: query.broadcast_validation as BroadcastValidation, + }; + }, + writeReqSsz: ({signedBlockOrContents, broadcastValidation}) => { + const slot = isSignedBlockContents(signedBlockOrContents) + ? signedBlockOrContents.signedBlock.message.slot + : signedBlockOrContents.message.slot; + return { + body: + config.getForkSeq(slot) < ForkSeq.deneb + ? config + .getForkTypes(slot) + .SignedBeaconBlock.serialize(signedBlockOrContents as allForks.SignedBeaconBlock) + : SignedBlockContentsType.serialize(signedBlockOrContents as SignedBlockContents), + headers: { + [MetaHeader.Version]: config.getForkName(slot), + }, + query: {broadcast_validation: broadcastValidation}, + }; + }, + parseReqSsz: ({body, headers, query}) => { + 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), + broadcastValidation: query.broadcast_validation as BroadcastValidation, + }; + }, + schema: { + body: Schema.Object, + query: {broadcast_validation: Schema.String}, + headers: {[MetaHeader.Version]: Schema.String}, + }, + }, + resp: EmptyResponseCodec, + init: { + requestWireFormat: WireFormat.ssz, + }, + }, + publishBlindedBlock: { + url: "/eth/v1/beacon/blinded_blocks", + method: "POST", + req: { + writeReqJson: ({signedBlindedBlock}) => { + const fork = config.getForkName(signedBlindedBlock.message.slot); + return { + body: getBlindedForkTypes(fork).SignedBeaconBlock.toJson(signedBlindedBlock), + headers: { + [MetaHeader.Version]: fork, + }, + }; + }, + parseReqJson: ({body, headers}) => { + const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); + return { + signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.fromJson(body), + }; + }, + writeReqSsz: ({signedBlindedBlock}) => { + const fork = config.getForkName(signedBlindedBlock.message.slot); + return { + body: getBlindedForkTypes(fork).SignedBeaconBlock.serialize(signedBlindedBlock), + headers: { + [MetaHeader.Version]: fork, + }, + }; + }, + parseReqSsz: ({body, headers}) => { + const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); + return { + signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.deserialize(body), + }; + }, + schema: { + body: Schema.Object, + headers: {[MetaHeader.Version]: Schema.String}, + }, + }, + resp: EmptyResponseCodec, + init: { + requestWireFormat: WireFormat.ssz, }, }, - publishBlindedBlock: reqOnlyBody(AllForksSignedBlindedBlock, Schema.Object), publishBlindedBlockV2: { - writeReq: (item, {broadcastValidation}) => ({ - body: AllForksSignedBlindedBlock.toJson(item), - query: {broadcast_validation: broadcastValidation}, - headers: {"Eth-Consensus-Version": config.getForkName(item.message.slot)}, - }), - parseReq: ({body, query}) => [ - AllForksSignedBlindedBlock.fromJson(body), - {broadcastValidation: query.broadcast_validation as BroadcastValidation}, - ], - schema: { - body: Schema.Object, - query: {broadcast_validation: Schema.String}, + url: "/eth/v2/beacon/blinded_blocks", + method: "POST", + req: { + writeReqJson: ({signedBlindedBlock, broadcastValidation}) => { + const fork = config.getForkName(signedBlindedBlock.message.slot); + return { + body: getBlindedForkTypes(fork).SignedBeaconBlock.toJson(signedBlindedBlock), + + headers: { + [MetaHeader.Version]: fork, + }, + query: {broadcast_validation: broadcastValidation}, + }; + }, + parseReqJson: ({body, headers, query}) => { + const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); + return { + signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.fromJson(body), + broadcastValidation: query.broadcast_validation as BroadcastValidation, + }; + }, + writeReqSsz: ({signedBlindedBlock, broadcastValidation}) => { + const fork = config.getForkName(signedBlindedBlock.message.slot); + return { + body: getBlindedForkTypes(fork).SignedBeaconBlock.serialize(signedBlindedBlock), + headers: { + [MetaHeader.Version]: fork, + }, + query: {broadcast_validation: broadcastValidation}, + }; + }, + parseReqSsz: ({body, headers, query}) => { + const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); + return { + signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.deserialize(body), + broadcastValidation: query.broadcast_validation as BroadcastValidation, + }; + }, + schema: { + body: Schema.Object, + query: {broadcast_validation: Schema.String}, + headers: {[MetaHeader.Version]: Schema.String}, + }, + }, + resp: EmptyResponseCodec, + init: { + requestWireFormat: WireFormat.ssz, }, }, getBlobSidecars: { - writeReq: (block_id, indices) => ({ - params: {block_id: String(block_id)}, - query: {indices}, - }), - parseReq: ({params, query}) => [params.block_id, query.indices], - schema: { - params: {block_id: Schema.StringRequired}, - query: {indices: Schema.UintArray}, + url: "/eth/v1/beacon/blob_sidecars/{block_id}", + method: "GET", + req: { + writeReq: ({blockId, indices}) => ({params: {block_id: blockId.toString()}, query: {indices}}), + parseReq: ({params, query}) => ({blockId: params.block_id, indices: query.indices}), + schema: {params: {block_id: Schema.StringRequired}, query: {indices: Schema.UintArray}}, + }, + resp: { + data: ssz.deneb.BlobSidecars, + meta: ExecutionOptimisticFinalizedAndVersionCodec, }, }, }; } - -export function getReturnTypes(): ReturnTypes { - const BeaconHeaderResType = new ContainerType({ - root: ssz.Root, - canonical: ssz.Boolean, - header: ssz.phase0.SignedBeaconBlockHeader, - }); - - const RootContainer = new ContainerType({ - root: ssz.Root, - }); - - return { - getBlock: ContainerData(ssz.phase0.SignedBeaconBlock), - getBlockV2: WithFinalized(WithExecutionOptimistic(WithVersion((fork) => ssz[fork].SignedBeaconBlock))), - getBlockAttestations: WithFinalized(ContainerDataExecutionOptimistic(ArrayOf(ssz.phase0.Attestation))), - getBlockHeader: WithFinalized(ContainerDataExecutionOptimistic(BeaconHeaderResType)), - getBlockHeaders: WithFinalized(ContainerDataExecutionOptimistic(ArrayOf(BeaconHeaderResType))), - getBlockRoot: WithFinalized(ContainerDataExecutionOptimistic(RootContainer)), - getBlobSidecars: WithFinalized(ContainerDataExecutionOptimistic(ssz.deneb.BlobSidecars)), - }; -} diff --git a/packages/api/src/beacon/routes/beacon/index.ts b/packages/api/src/beacon/routes/beacon/index.ts index 95c78f45ea39..f70792f9d76f 100644 --- a/packages/api/src/beacon/routes/beacon/index.ts +++ b/packages/api/src/beacon/routes/beacon/index.ts @@ -1,8 +1,7 @@ import {ChainForkConfig} from "@lodestar/config"; import {phase0, ssz} from "@lodestar/types"; -import {ApiClientResponse} from "../../../interfaces.js"; -import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; -import {RoutesData, ReturnTypes, reqEmpty, ContainerData} from "../../../utils/index.js"; +import {Endpoint, RouteDefinitions} from "../../../utils/types.js"; +import {EmptyArgs, EmptyRequestCodec, EmptyMeta, EmptyMetaCodec, EmptyRequest} from "../../../utils/codecs.js"; import * as block from "./block.js"; import * as pool from "./pool.js"; import * as state from "./state.js"; @@ -10,12 +9,11 @@ import * as rewards from "./rewards.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -// NOTE: We choose to split the block, pool, and state namespaces so the files are not too big. +// NOTE: We choose to split the block, pool, state and rewards namespaces so the files are not too big. // However, for a consumer all these methods are within the same service "beacon" export {block, pool, state, rewards}; export {BroadcastValidation} from "./block.js"; export type {BlockId, BlockHeaderResponse} from "./block.js"; -export type {AttestationFilters} from "./pool.js"; export type { BlockRewards, AttestationsRewards, @@ -28,8 +26,6 @@ export type { StateId, ValidatorId, ValidatorStatus, - ValidatorFilters, - CommitteesFilters, FinalityCheckpoints, ValidatorResponse, ValidatorBalance, @@ -37,42 +33,34 @@ export type { EpochSyncCommitteeResponse, } from "./state.js"; -export type Api = block.Api & - pool.Api & - state.Api & - rewards.Api & { - getGenesis(): Promise>; +export type Endpoints = block.Endpoints & + pool.Endpoints & + state.Endpoints & + rewards.Endpoints & { + getGenesis: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + phase0.Genesis, + EmptyMeta + >; }; -export const routesData: RoutesData = { - getGenesis: {url: "/eth/v1/beacon/genesis", method: "GET"}, - ...block.routesData, - ...pool.routesData, - ...state.routesData, - ...rewards.routesData, -}; - -export type ReqTypes = { - [K in keyof ReturnType]: ReturnType[K]["writeReq"]>; -}; - -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function getReqSerializers(config: ChainForkConfig) { - return { - getGenesis: reqEmpty, - ...block.getReqSerializers(config), - ...pool.getReqSerializers(), - ...state.getReqSerializers(), - ...rewards.getReqSerializers(), - }; -} - -export function getReturnTypes(): ReturnTypes { +export function getDefinitions(config: ChainForkConfig): RouteDefinitions { return { - getGenesis: ContainerData(ssz.phase0.Genesis), - ...block.getReturnTypes(), - ...pool.getReturnTypes(), - ...state.getReturnTypes(), - ...rewards.getReturnTypes(), + getGenesis: { + url: "/eth/v1/beacon/genesis", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: ssz.phase0.Genesis, + meta: EmptyMetaCodec, + }, + }, + ...block.getDefinitions(config), + ...pool.getDefinitions(config), + ...state.getDefinitions(config), + ...rewards.getDefinitions(config), }; } diff --git a/packages/api/src/beacon/routes/beacon/pool.ts b/packages/api/src/beacon/routes/beacon/pool.ts index 65467e98b721..f957390131fe 100644 --- a/packages/api/src/beacon/routes/beacon/pool.ts +++ b/packages/api/src/beacon/routes/beacon/pool.ts @@ -1,70 +1,98 @@ -import {phase0, altair, capella, CommitteeIndex, Slot, ssz} from "@lodestar/types"; -import {ApiClientResponse} from "../../../interfaces.js"; -import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {ValueOf} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; +import {phase0, capella, CommitteeIndex, Slot, ssz} from "@lodestar/types"; +import {Schema, Endpoint, RouteDefinitions} from "../../../utils/index.js"; import { - RoutesData, - ReturnTypes, ArrayOf, - Schema, - reqOnlyBody, - ReqSerializers, - reqEmpty, - ReqEmpty, - ContainerData, -} from "../../../utils/index.js"; + EmptyArgs, + EmptyRequestCodec, + EmptyMeta, + EmptyMetaCodec, + EmptyRequest, + EmptyResponseCodec, + EmptyResponseData, +} from "../../../utils/codecs.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -export type AttestationFilters = { - slot: Slot; - committeeIndex: CommitteeIndex; -}; +const AttestationListType = ArrayOf(ssz.phase0.Attestation); +const AttesterSlashingListType = ArrayOf(ssz.phase0.AttesterSlashing); +const ProposerSlashingListType = ArrayOf(ssz.phase0.ProposerSlashing); +const SignedVoluntaryExitListType = ArrayOf(ssz.phase0.SignedVoluntaryExit); +const SignedBLSToExecutionChangeListType = ArrayOf(ssz.capella.SignedBLSToExecutionChange); +const SyncCommitteeMessageListType = ArrayOf(ssz.altair.SyncCommitteeMessage); + +type AttestationList = ValueOf; +type AttesterSlashingList = ValueOf; +type ProposerSlashingList = ValueOf; +type SignedVoluntaryExitList = ValueOf; +type SignedBLSToExecutionChangeList = ValueOf; +type SyncCommitteeMessageList = ValueOf; -export type Api = { +export type Endpoints = { /** * Get Attestations from operations pool * Retrieves attestations known by the node but not necessarily incorporated into any block - * @param slot - * @param committeeIndex - * @returns any Successful response - * @throws ApiError */ - getPoolAttestations( - filters?: Partial - ): Promise>; + getPoolAttestations: Endpoint< + "GET", + {slot?: Slot; committeeIndex?: CommitteeIndex}, + {query: {slot?: number; committee_index?: number}}, + AttestationList, + EmptyMeta + >; /** * Get AttesterSlashings from operations pool * Retrieves attester slashings known by the node but not necessarily incorporated into any block - * @returns any Successful response - * @throws ApiError */ - getPoolAttesterSlashings(): Promise>; + getPoolAttesterSlashings: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + AttesterSlashingList, + EmptyMeta + >; /** * Get ProposerSlashings from operations pool * Retrieves proposer slashings known by the node but not necessarily incorporated into any block - * @returns any Successful response - * @throws ApiError */ - getPoolProposerSlashings(): Promise>; + getPoolProposerSlashings: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + ProposerSlashingList, + EmptyMeta + >; /** * Get SignedVoluntaryExit from operations pool * Retrieves voluntary exits known by the node but not necessarily incorporated into any block - * @returns any Successful response - * @throws ApiError */ - getPoolVoluntaryExits(): Promise>; + getPoolVoluntaryExits: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + SignedVoluntaryExitList, + EmptyMeta + >; /** * Get SignedBLSToExecutionChange from operations pool * Retrieves BLSToExecutionChange known by the node but not necessarily incorporated into any block - * @returns any Successful response - * @throws ApiError */ - getPoolBlsToExecutionChanges(): Promise< - ApiClientResponse<{[HttpStatusCode.OK]: {data: capella.SignedBLSToExecutionChange[]}}> + getPoolBLSToExecutionChanges: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + SignedBLSToExecutionChangeList, + EmptyMeta >; /** @@ -74,125 +102,214 @@ export type Api = { * If an attestation is validated successfully the node MUST publish that attestation on the appropriate subnet. * * If one or more attestations fail validation the node MUST return a 400 error with details of which attestations have failed, and why. - * - * @param requestBody - * @returns any Attestations are stored in pool and broadcast on appropriate subnet - * @throws ApiError */ - submitPoolAttestations( - attestations: phase0.Attestation[] - ): Promise>; + submitPoolAttestations: Endpoint< + "POST", + {signedAttestations: AttestationList}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; /** * Submit AttesterSlashing object to node's pool * Submits AttesterSlashing object to node's pool and if passes validation node MUST broadcast it to network. - * @param requestBody - * @returns any Success - * @throws ApiError */ - submitPoolAttesterSlashings( - slashing: phase0.AttesterSlashing - ): Promise>; + submitPoolAttesterSlashings: Endpoint< + "POST", + {attesterSlashing: phase0.AttesterSlashing}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; /** * Submit ProposerSlashing object to node's pool * Submits ProposerSlashing object to node's pool and if passes validation node MUST broadcast it to network. - * @param requestBody - * @returns any Success - * @throws ApiError */ - submitPoolProposerSlashings( - slashing: phase0.ProposerSlashing - ): Promise>; + submitPoolProposerSlashings: Endpoint< + "POST", + {proposerSlashing: phase0.ProposerSlashing}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; /** * Submit SignedVoluntaryExit object to node's pool * Submits SignedVoluntaryExit object to node's pool and if passes validation node MUST broadcast it to network. - * @param requestBody - * @returns any Voluntary exit is stored in node and broadcasted to network - * @throws ApiError */ - submitPoolVoluntaryExit( - exit: phase0.SignedVoluntaryExit - ): Promise>; + submitPoolVoluntaryExit: Endpoint< + "POST", + {signedVoluntaryExit: phase0.SignedVoluntaryExit}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; /** - * Submit SignedBLSToExecutionChange object to node's pool - * Submits SignedBLSToExecutionChange object to node's pool and if passes validation node MUST broadcast it to network. - * @param requestBody - * @returns any BLSToExecutionChange is stored in node and broadcasted to network - * @throws ApiError + * Submit SignedBLSToExecutionChange objects to node's pool + * Submits SignedBLSToExecutionChange objects to node's pool and if passes validation node MUST broadcast it to network. */ - submitPoolBlsToExecutionChange( - blsToExecutionChange: capella.SignedBLSToExecutionChange[] - ): Promise>; + submitPoolBLSToExecutionChange: Endpoint< + "POST", + {blsToExecutionChanges: capella.SignedBLSToExecutionChange[]}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; /** - * TODO: Add description + * Submit SyncCommitteeMessage objects to node's pool + * Submits SyncCommitteeMessage objects to node's pool and if passes validation node MUST broadcast it to network. */ - submitPoolSyncCommitteeSignatures( - signatures: altair.SyncCommitteeMessage[] - ): Promise>; -}; - -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - getPoolAttestations: {url: "/eth/v1/beacon/pool/attestations", method: "GET"}, - getPoolAttesterSlashings: {url: "/eth/v1/beacon/pool/attester_slashings", method: "GET"}, - getPoolProposerSlashings: {url: "/eth/v1/beacon/pool/proposer_slashings", method: "GET"}, - getPoolVoluntaryExits: {url: "/eth/v1/beacon/pool/voluntary_exits", method: "GET"}, - getPoolBlsToExecutionChanges: {url: "/eth/v1/beacon/pool/bls_to_execution_changes", method: "GET"}, - submitPoolAttestations: {url: "/eth/v1/beacon/pool/attestations", method: "POST"}, - submitPoolAttesterSlashings: {url: "/eth/v1/beacon/pool/attester_slashings", method: "POST"}, - submitPoolProposerSlashings: {url: "/eth/v1/beacon/pool/proposer_slashings", method: "POST"}, - submitPoolVoluntaryExit: {url: "/eth/v1/beacon/pool/voluntary_exits", method: "POST"}, - submitPoolBlsToExecutionChange: {url: "/eth/v1/beacon/pool/bls_to_execution_changes", method: "POST"}, - submitPoolSyncCommitteeSignatures: {url: "/eth/v1/beacon/pool/sync_committees", method: "POST"}, -}; - -/* eslint-disable @typescript-eslint/naming-convention */ -export type ReqTypes = { - getPoolAttestations: {query: {slot?: number; committee_index?: number}}; - getPoolAttesterSlashings: ReqEmpty; - getPoolProposerSlashings: ReqEmpty; - getPoolVoluntaryExits: ReqEmpty; - getPoolBlsToExecutionChanges: ReqEmpty; - submitPoolAttestations: {body: unknown}; - submitPoolAttesterSlashings: {body: unknown}; - submitPoolProposerSlashings: {body: unknown}; - submitPoolVoluntaryExit: {body: unknown}; - submitPoolBlsToExecutionChange: {body: unknown}; - submitPoolSyncCommitteeSignatures: {body: unknown}; + submitPoolSyncCommitteeSignatures: Endpoint< + "POST", + {signatures: SyncCommitteeMessageList}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; }; -export function getReqSerializers(): ReqSerializers { +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { getPoolAttestations: { - writeReq: (filters) => ({query: {slot: filters?.slot, committee_index: filters?.committeeIndex}}), - parseReq: ({query}) => [{slot: query.slot, committeeIndex: query.committee_index}], - schema: {query: {slot: Schema.Uint, committee_index: Schema.Uint}}, + url: "/eth/v1/beacon/pool/attestations", + method: "GET", + req: { + writeReq: ({slot, committeeIndex}) => ({query: {slot, committee_index: committeeIndex}}), + parseReq: ({query}) => ({slot: query.slot, committeeIndex: query.committee_index}), + schema: {query: {slot: Schema.Uint, committee_index: Schema.Uint}}, + }, + resp: { + data: AttestationListType, + meta: EmptyMetaCodec, + }, + }, + getPoolAttesterSlashings: { + url: "/eth/v1/beacon/pool/attester_slashings", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: AttesterSlashingListType, + meta: EmptyMetaCodec, + }, + }, + getPoolProposerSlashings: { + url: "/eth/v1/beacon/pool/proposer_slashings", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: ProposerSlashingListType, + meta: EmptyMetaCodec, + }, + }, + getPoolVoluntaryExits: { + url: "/eth/v1/beacon/pool/voluntary_exits", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: SignedVoluntaryExitListType, + meta: EmptyMetaCodec, + }, + }, + getPoolBLSToExecutionChanges: { + url: "/eth/v1/beacon/pool/bls_to_execution_changes", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: SignedBLSToExecutionChangeListType, + meta: EmptyMetaCodec, + }, + }, + submitPoolAttestations: { + url: "/eth/v1/beacon/pool/attestations", + method: "POST", + req: { + writeReqJson: ({signedAttestations}) => ({body: AttestationListType.toJson(signedAttestations)}), + parseReqJson: ({body}) => ({signedAttestations: AttestationListType.fromJson(body)}), + writeReqSsz: ({signedAttestations}) => ({body: AttestationListType.serialize(signedAttestations)}), + parseReqSsz: ({body}) => ({signedAttestations: AttestationListType.deserialize(body)}), + schema: { + body: Schema.ObjectArray, + }, + }, + resp: EmptyResponseCodec, + }, + submitPoolAttesterSlashings: { + url: "/eth/v1/beacon/pool/attester_slashings", + method: "POST", + req: { + writeReqJson: ({attesterSlashing}) => ({body: ssz.phase0.AttesterSlashing.toJson(attesterSlashing)}), + parseReqJson: ({body}) => ({attesterSlashing: ssz.phase0.AttesterSlashing.fromJson(body)}), + writeReqSsz: ({attesterSlashing}) => ({body: ssz.phase0.AttesterSlashing.serialize(attesterSlashing)}), + parseReqSsz: ({body}) => ({attesterSlashing: ssz.phase0.AttesterSlashing.deserialize(body)}), + schema: { + body: Schema.Object, + }, + }, + resp: EmptyResponseCodec, + }, + submitPoolProposerSlashings: { + url: "/eth/v1/beacon/pool/proposer_slashings", + method: "POST", + req: { + writeReqJson: ({proposerSlashing}) => ({body: ssz.phase0.ProposerSlashing.toJson(proposerSlashing)}), + parseReqJson: ({body}) => ({proposerSlashing: ssz.phase0.ProposerSlashing.fromJson(body)}), + writeReqSsz: ({proposerSlashing}) => ({body: ssz.phase0.ProposerSlashing.serialize(proposerSlashing)}), + parseReqSsz: ({body}) => ({proposerSlashing: ssz.phase0.ProposerSlashing.deserialize(body)}), + schema: { + body: Schema.Object, + }, + }, + resp: EmptyResponseCodec, + }, + submitPoolVoluntaryExit: { + url: "/eth/v1/beacon/pool/voluntary_exits", + method: "POST", + req: { + writeReqJson: ({signedVoluntaryExit}) => ({body: ssz.phase0.SignedVoluntaryExit.toJson(signedVoluntaryExit)}), + parseReqJson: ({body}) => ({signedVoluntaryExit: ssz.phase0.SignedVoluntaryExit.fromJson(body)}), + writeReqSsz: ({signedVoluntaryExit}) => ({body: ssz.phase0.SignedVoluntaryExit.serialize(signedVoluntaryExit)}), + parseReqSsz: ({body}) => ({signedVoluntaryExit: ssz.phase0.SignedVoluntaryExit.deserialize(body)}), + schema: { + body: Schema.Object, + }, + }, + resp: EmptyResponseCodec, + }, + submitPoolBLSToExecutionChange: { + url: "/eth/v1/beacon/pool/bls_to_execution_changes", + method: "POST", + req: { + writeReqJson: ({blsToExecutionChanges}) => ({ + body: SignedBLSToExecutionChangeListType.toJson(blsToExecutionChanges), + }), + parseReqJson: ({body}) => ({blsToExecutionChanges: SignedBLSToExecutionChangeListType.fromJson(body)}), + writeReqSsz: ({blsToExecutionChanges}) => ({ + body: SignedBLSToExecutionChangeListType.serialize(blsToExecutionChanges), + }), + parseReqSsz: ({body}) => ({blsToExecutionChanges: SignedBLSToExecutionChangeListType.deserialize(body)}), + schema: { + body: Schema.ObjectArray, + }, + }, + resp: EmptyResponseCodec, + }, + submitPoolSyncCommitteeSignatures: { + url: "/eth/v1/beacon/pool/sync_committees", + method: "POST", + req: { + writeReqJson: ({signatures}) => ({body: SyncCommitteeMessageListType.toJson(signatures)}), + parseReqJson: ({body}) => ({signatures: SyncCommitteeMessageListType.fromJson(body)}), + writeReqSsz: ({signatures}) => ({body: SyncCommitteeMessageListType.serialize(signatures)}), + parseReqSsz: ({body}) => ({signatures: SyncCommitteeMessageListType.deserialize(body)}), + schema: { + body: Schema.ObjectArray, + }, + }, + resp: EmptyResponseCodec, }, - getPoolAttesterSlashings: reqEmpty, - getPoolProposerSlashings: reqEmpty, - getPoolVoluntaryExits: reqEmpty, - getPoolBlsToExecutionChanges: reqEmpty, - submitPoolAttestations: reqOnlyBody(ArrayOf(ssz.phase0.Attestation), Schema.ObjectArray), - submitPoolAttesterSlashings: reqOnlyBody(ssz.phase0.AttesterSlashing, Schema.Object), - submitPoolProposerSlashings: reqOnlyBody(ssz.phase0.ProposerSlashing, Schema.Object), - submitPoolVoluntaryExit: reqOnlyBody(ssz.phase0.SignedVoluntaryExit, Schema.Object), - submitPoolBlsToExecutionChange: reqOnlyBody(ArrayOf(ssz.capella.SignedBLSToExecutionChange), Schema.ObjectArray), - submitPoolSyncCommitteeSignatures: reqOnlyBody(ArrayOf(ssz.altair.SyncCommitteeMessage), Schema.ObjectArray), - }; -} - -export function getReturnTypes(): ReturnTypes { - return { - getPoolAttestations: ContainerData(ArrayOf(ssz.phase0.Attestation)), - getPoolAttesterSlashings: ContainerData(ArrayOf(ssz.phase0.AttesterSlashing)), - getPoolProposerSlashings: ContainerData(ArrayOf(ssz.phase0.ProposerSlashing)), - getPoolVoluntaryExits: ContainerData(ArrayOf(ssz.phase0.SignedVoluntaryExit)), - getPoolBlsToExecutionChanges: ContainerData(ArrayOf(ssz.capella.SignedBLSToExecutionChange)), }; } diff --git a/packages/api/src/beacon/routes/beacon/rewards.ts b/packages/api/src/beacon/routes/beacon/rewards.ts index f2136760c290..c65282625343 100644 --- a/packages/api/src/beacon/routes/beacon/rewards.ts +++ b/packages/api/src/beacon/routes/beacon/rewards.ts @@ -1,82 +1,107 @@ -import {ContainerType} from "@chainsafe/ssz"; -import {Epoch, ssz, ValidatorIndex} from "@lodestar/types"; - -import { - RoutesData, - ReturnTypes, - Schema, - ReqSerializers, - ContainerDataExecutionOptimistic, - ArrayOf, - WithFinalized, -} from "../../../utils/index.js"; -import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; -import {ApiClientResponse} from "../../../interfaces.js"; -import {BlockId} from "./block.js"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {ContainerType, ValueOf} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; +import {Epoch, ssz} from "@lodestar/types"; + +import {Schema, Endpoint, RouteDefinitions} from "../../../utils/index.js"; +import {fromValidatorIdsStr, toValidatorIdsStr} from "../../../utils/serdes.js"; +import {ArrayOf, JsonOnlyReq} from "../../../utils/codecs.js"; +import {ExecutionOptimisticAndFinalizedCodec, ExecutionOptimisticAndFinalizedMeta} from "../../../utils/metadata.js"; +import {BlockArgs} from "./block.js"; import {ValidatorId} from "./state.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -/** - * True if the response references an unverified execution payload. Optimistic information may be invalidated at - * a later time. If the field is not present, assume the False value. - */ -export type ExecutionOptimistic = boolean; - -/** - * True if the response references the finalized history of the chain, as determined by fork choice. - */ -export type Finalized = boolean; +const BlockRewardsType = new ContainerType( + { + /** Proposer of the block, the proposer index who receives these rewards */ + proposerIndex: ssz.ValidatorIndex, + /** Total block reward, equal to attestations + sync_aggregate + proposer_slashings + attester_slashings */ + total: ssz.UintNum64, + /** Block reward component due to included attestations */ + attestations: ssz.UintNum64, + /** Block reward component due to included sync_aggregate */ + syncAggregate: ssz.UintNum64, + /** Block reward component due to included proposer_slashings */ + proposerSlashings: ssz.UintNum64, + /** Block reward component due to included attester_slashings */ + attesterSlashings: ssz.UintNum64, + }, + {jsonCase: "eth2"} +); + +const AttestationsRewardType = new ContainerType( + { + /** Reward for head vote. Could be negative to indicate penalty */ + head: ssz.UintNum64, + /** Reward for target vote. Could be negative to indicate penalty */ + target: ssz.UintNum64, + /** Reward for source vote. Could be negative to indicate penalty */ + source: ssz.UintNum64, + /** Inclusion delay reward (phase0 only) */ + inclusionDelay: ssz.UintNum64, + /** Inactivity penalty. Should be a negative number to indicate penalty */ + inactivity: ssz.UintNum64, + }, + {jsonCase: "eth2"} +); + +const IdealAttestationsRewardsType = new ContainerType( + { + ...AttestationsRewardType.fields, + effectiveBalance: ssz.UintNum64, + }, + {jsonCase: "eth2"} +); + +const TotalAttestationsRewardsType = new ContainerType( + { + ...AttestationsRewardType.fields, + validatorIndex: ssz.ValidatorIndex, + }, + {jsonCase: "eth2"} +); + +const AttestationsRewardsType = new ContainerType( + { + idealRewards: ArrayOf(IdealAttestationsRewardsType), + totalRewards: ArrayOf(TotalAttestationsRewardsType), + }, + {jsonCase: "eth2"} +); + +const SyncCommitteeRewardsType = ArrayOf( + new ContainerType( + { + validatorIndex: ssz.ValidatorIndex, + reward: ssz.UintNum64, + }, + {jsonCase: "eth2"} + ) +); /** * Rewards info for a single block. Every reward value is in Gwei. */ -export type BlockRewards = { - /** Proposer of the block, the proposer index who receives these rewards */ - proposerIndex: ValidatorIndex; - /** Total block reward, equal to attestations + sync_aggregate + proposer_slashings + attester_slashings */ - total: number; - /** Block reward component due to included attestations */ - attestations: number; - /** Block reward component due to included sync_aggregate */ - syncAggregate: number; - /** Block reward component due to included proposer_slashings */ - proposerSlashings: number; - /** Block reward component due to included attester_slashings */ - attesterSlashings: number; -}; +export type BlockRewards = ValueOf; /** * Rewards for a single set of (ideal or actual depending on usage) attestations. Reward value is in Gwei */ -type AttestationsReward = { - /** Reward for head vote. Could be negative to indicate penalty */ - head: number; - /** Reward for target vote. Could be negative to indicate penalty */ - target: number; - /** Reward for source vote. Could be negative to indicate penalty */ - source: number; - /** Inclusion delay reward (phase0 only) */ - inclusionDelay: number; - /** Inactivity penalty. Should be a negative number to indicate penalty */ - inactivity: number; -}; +export type AttestationsReward = ValueOf; /** * Rewards info for ideal attestations ie. Maximum rewards could be earned by making timely head, target and source vote. * `effectiveBalance` is in Gwei */ -export type IdealAttestationsReward = AttestationsReward & {effectiveBalance: number}; +export type IdealAttestationsReward = ValueOf; /** * Rewards info for actual attestations */ -export type TotalAttestationsReward = AttestationsReward & {validatorIndex: ValidatorIndex}; +export type TotalAttestationsReward = ValueOf; -export type AttestationsRewards = { - idealRewards: IdealAttestationsReward[]; - totalRewards: TotalAttestationsReward[]; -}; +export type AttestationsRewards = ValueOf; /** * Rewards info for sync committee participation. Every reward value is in Gwei. @@ -84,174 +109,112 @@ export type AttestationsRewards = { * participating in sync committee. Please refer to `BlockRewards.syncAggregate` for rewards of proposer including sync committee * outputs into their block */ -export type SyncCommitteeRewards = {validatorIndex: ValidatorIndex; reward: number}[]; +export type SyncCommitteeRewards = ValueOf; -export type Api = { +export type Endpoints = { /** * Get block rewards * Returns the info of rewards received by the block proposer - * - * @param blockId Block identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. */ - getBlockRewards(blockId: BlockId): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: BlockRewards; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getBlockRewards: Endpoint< + "GET", + BlockArgs, + {params: {block_id: string}}, + BlockRewards, + ExecutionOptimisticAndFinalizedMeta >; + /** * Get attestations rewards * Negative values indicate penalties. `inactivity` can only be either 0 or negative number since it is penalty only - * - * @param epoch The epoch to get rewards info from - * @param validatorIds List of validator indices or pubkeys to filter in */ - getAttestationsRewards( - epoch: Epoch, - validatorIds?: ValidatorId[] - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: AttestationsRewards; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getAttestationsRewards: Endpoint< + "POST", + { + /** The epoch to get rewards info from */ + epoch: Epoch; + /** List of validator indices or pubkeys to filter in */ + validatorIds?: ValidatorId[]; + }, + {params: {epoch: number}; body: string[]}, + AttestationsRewards, + ExecutionOptimisticAndFinalizedMeta >; /** * Get sync committee rewards * Returns participant reward value for each sync committee member at the given block. - * - * @param blockId Block identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \, \. - * @param validatorIds List of validator indices or pubkeys to filter in */ - getSyncCommitteeRewards( - blockId: BlockId, - validatorIds?: ValidatorId[] - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: SyncCommitteeRewards; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getSyncCommitteeRewards: Endpoint< + "POST", + BlockArgs & { + /** List of validator indices or pubkeys to filter in */ + validatorIds?: ValidatorId[]; + }, + {params: {block_id: string}; body: string[]}, + SyncCommitteeRewards, + ExecutionOptimisticAndFinalizedMeta >; }; -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - getBlockRewards: {url: "/eth/v1/beacon/rewards/blocks/{block_id}", method: "GET"}, - getAttestationsRewards: {url: "/eth/v1/beacon/rewards/attestations/{epoch}", method: "POST"}, - getSyncCommitteeRewards: {url: "/eth/v1/beacon/rewards/sync_committee/{block_id}", method: "POST"}, -}; - -export type ReqTypes = { - /* eslint-disable @typescript-eslint/naming-convention */ - getBlockRewards: {params: {block_id: string}}; - getAttestationsRewards: {params: {epoch: number}; body: ValidatorId[]}; - getSyncCommitteeRewards: {params: {block_id: string}; body: ValidatorId[]}; -}; - -export function getReqSerializers(): ReqSerializers { +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { getBlockRewards: { - writeReq: (block_id) => ({params: {block_id: String(block_id)}}), - parseReq: ({params}) => [params.block_id], - schema: {params: {block_id: Schema.StringRequired}}, + url: "/eth/v1/beacon/rewards/blocks/{block_id}", + method: "GET", + req: { + writeReq: ({blockId}) => ({params: {block_id: blockId.toString()}}), + parseReq: ({params}) => ({blockId: params.block_id}), + schema: {params: {block_id: Schema.StringRequired}}, + }, + resp: { + data: BlockRewardsType, + meta: ExecutionOptimisticAndFinalizedCodec, + }, }, getAttestationsRewards: { - writeReq: (epoch, validatorIds) => ({params: {epoch: epoch}, body: validatorIds || []}), - parseReq: ({params, body}) => [params.epoch, body], - schema: { - params: {epoch: Schema.UintRequired}, - body: Schema.UintOrStringArray, + url: "/eth/v1/beacon/rewards/attestations/{epoch}", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({epoch, validatorIds}) => ({ + params: {epoch}, + body: toValidatorIdsStr(validatorIds) || [], + }), + parseReqJson: ({params, body}) => ({ + epoch: params.epoch, + validatorIds: fromValidatorIdsStr(body), + }), + schema: { + params: {epoch: Schema.UintRequired}, + body: Schema.UintOrStringArray, + }, + }), + resp: { + data: AttestationsRewardsType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, getSyncCommitteeRewards: { - writeReq: (block_id, validatorIds) => ({params: {block_id: String(block_id)}, body: validatorIds || []}), - parseReq: ({params, body}) => [params.block_id, body], - schema: { - params: {block_id: Schema.StringRequired}, - body: Schema.UintOrStringArray, + url: "/eth/v1/beacon/rewards/sync_committee/{block_id}", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({blockId, validatorIds}) => ({ + params: {block_id: blockId.toString()}, + body: toValidatorIdsStr(validatorIds) || [], + }), + parseReqJson: ({params, body}) => ({ + blockId: params.block_id, + validatorIds: fromValidatorIdsStr(body), + }), + schema: { + params: {block_id: Schema.StringRequired}, + body: Schema.UintOrStringArray, + }, + }), + resp: { + data: SyncCommitteeRewardsType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, }; } - -export function getReturnTypes(): ReturnTypes { - const BlockRewardsResponse = new ContainerType( - { - proposerIndex: ssz.ValidatorIndex, - total: ssz.UintNum64, - attestations: ssz.UintNum64, - syncAggregate: ssz.UintNum64, - proposerSlashings: ssz.UintNum64, - attesterSlashings: ssz.UintNum64, - }, - {jsonCase: "eth2"} - ); - - const IdealAttestationsRewardsResponse = new ContainerType( - { - head: ssz.UintNum64, - target: ssz.UintNum64, - source: ssz.UintNum64, - inclusionDelay: ssz.UintNum64, - inactivity: ssz.UintNum64, - effectiveBalance: ssz.UintNum64, - }, - {jsonCase: "eth2"} - ); - - const TotalAttestationsRewardsResponse = new ContainerType( - { - head: ssz.UintNum64, - target: ssz.UintNum64, - source: ssz.UintNum64, - inclusionDelay: ssz.UintNum64, - inactivity: ssz.UintNum64, - validatorIndex: ssz.ValidatorIndex, - }, - {jsonCase: "eth2"} - ); - - const AttestationsRewardsResponse = new ContainerType( - { - idealRewards: ArrayOf(IdealAttestationsRewardsResponse), - totalRewards: ArrayOf(TotalAttestationsRewardsResponse), - }, - {jsonCase: "eth2"} - ); - - const SyncCommitteeRewardsResponse = new ContainerType( - { - validatorIndex: ssz.ValidatorIndex, - reward: ssz.UintNum64, - }, - {jsonCase: "eth2"} - ); - - return { - getBlockRewards: WithFinalized(ContainerDataExecutionOptimistic(BlockRewardsResponse)), - getAttestationsRewards: WithFinalized(ContainerDataExecutionOptimistic(AttestationsRewardsResponse)), - getSyncCommitteeRewards: WithFinalized(ContainerDataExecutionOptimistic(ArrayOf(SyncCommitteeRewardsResponse))), - }; -} diff --git a/packages/api/src/beacon/routes/beacon/state.ts b/packages/api/src/beacon/routes/beacon/state.ts index 0c3875f8a3de..4f8d414f5b6c 100644 --- a/packages/api/src/beacon/routes/beacon/state.ts +++ b/packages/api/src/beacon/routes/beacon/state.ts @@ -1,34 +1,28 @@ -import {ContainerType} from "@chainsafe/ssz"; -import {phase0, CommitteeIndex, Slot, ValidatorIndex, Epoch, Root, ssz, StringType, RootHex} from "@lodestar/types"; -import {ApiClientResponse} from "../../../interfaces.js"; -import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js"; -import {fromU64Str, toU64Str} from "../../../utils/serdes.js"; -import { - RoutesData, - ReturnTypes, - ArrayOf, - ContainerDataExecutionOptimistic, - Schema, - ReqSerializers, - ReqSerializer, - WithFinalized, -} from "../../../utils/index.js"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {ContainerType, ValueOf} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; +import {MAX_VALIDATORS_PER_COMMITTEE} from "@lodestar/params"; +import {phase0, CommitteeIndex, Slot, Epoch, ssz, RootHex, StringType} from "@lodestar/types"; +import {Endpoint, RequestCodec, RouteDefinitions, Schema} from "../../../utils/index.js"; +import {ArrayOf, JsonOnlyReq} from "../../../utils/codecs.js"; +import {ExecutionOptimisticAndFinalizedCodec, ExecutionOptimisticAndFinalizedMeta} from "../../../utils/metadata.js"; +import {fromValidatorIdsStr, toValidatorIdsStr} from "../../../utils/serdes.js"; +import {WireFormat} from "../../../utils/wireFormat.js"; +import {RootResponse, RootResponseType} from "./block.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes export type StateId = RootHex | Slot | "head" | "genesis" | "finalized" | "justified"; -export type ValidatorId = string | number; -/** - * True if the response references an unverified execution payload. Optimistic information may be invalidated at - * a later time. If the field is not present, assume the False value. - */ -export type ExecutionOptimistic = boolean; +export type StateArgs = { + /** + * State identifier. + * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. + */ + stateId: StateId; +}; -/** - * True if the response references the finalized history of the chain, as determined by fork choice. - */ -export type Finalized = boolean; +export type ValidatorId = string | number; export type ValidatorStatus = | "active" @@ -42,492 +36,411 @@ export type ValidatorStatus = | "withdrawal_possible" | "withdrawal_done"; -export type ValidatorFilters = { - id?: ValidatorId[]; - status?: ValidatorStatus[]; -}; -export type CommitteesFilters = { - epoch?: Epoch; - index?: CommitteeIndex; - slot?: Slot; -}; - -export type FinalityCheckpoints = { - previousJustified: phase0.Checkpoint; - currentJustified: phase0.Checkpoint; - finalized: phase0.Checkpoint; -}; - -export type ValidatorResponse = { - index: ValidatorIndex; - balance: number; - status: ValidatorStatus; - validator: phase0.Validator; -}; - -export type ValidatorBalance = { - index: ValidatorIndex; - balance: number; -}; - -export type EpochCommitteeResponse = { - index: CommitteeIndex; - slot: Slot; - validators: ArrayLike; -}; - -export type EpochSyncCommitteeResponse = { - /** all of the validator indices in the current sync committee */ - validators: ValidatorIndex[]; - // TODO: This property will likely be deprecated - /** Subcommittee slices of the current sync committee */ - validatorAggregates: ValidatorIndex[][]; -}; - -export type Api = { +export const RandaoResponseType = new ContainerType({ + randao: ssz.Root, +}); +export const FinalityCheckpointsType = new ContainerType( + { + previousJustified: ssz.phase0.Checkpoint, + currentJustified: ssz.phase0.Checkpoint, + finalized: ssz.phase0.Checkpoint, + }, + {jsonCase: "eth2"} +); +export const ValidatorResponseType = new ContainerType({ + index: ssz.ValidatorIndex, + balance: ssz.UintNum64, + status: new StringType(), + validator: ssz.phase0.Validator, +}); +export const EpochCommitteeResponseType = new ContainerType({ + index: ssz.CommitteeIndex, + slot: ssz.Slot, + validators: ArrayOf(ssz.ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE), +}); +export const ValidatorBalanceType = new ContainerType({ + index: ssz.ValidatorIndex, + balance: ssz.UintNum64, +}); +export const EpochSyncCommitteeResponseType = new ContainerType( + { + /** All of the validator indices in the current sync committee */ + validators: ArrayOf(ssz.ValidatorIndex), + // TODO: This property will likely be deprecated + /** Subcommittee slices of the current sync committee */ + validatorAggregates: ArrayOf(ArrayOf(ssz.ValidatorIndex)), + }, + {jsonCase: "eth2"} +); +export const ValidatorResponseListType = ArrayOf(ValidatorResponseType); +export const EpochCommitteeResponseListType = ArrayOf(EpochCommitteeResponseType); +export const ValidatorBalanceListType = ArrayOf(ValidatorBalanceType); + +export type RandaoResponse = ValueOf; +export type FinalityCheckpoints = ValueOf; +export type ValidatorResponse = ValueOf; +export type EpochCommitteeResponse = ValueOf; +export type ValidatorBalance = ValueOf; +export type EpochSyncCommitteeResponse = ValueOf; + +export type ValidatorResponseList = ValueOf; +export type EpochCommitteeResponseList = ValueOf; +export type ValidatorBalanceList = ValueOf; + +export type Endpoints = { /** * Get state SSZ HashTreeRoot * Calculates HashTreeRoot for state with given 'stateId'. If stateId is root, same value will be returned. - * - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. */ - getStateRoot(stateId: StateId): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: {root: Root}; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getStateRoot: Endpoint< + "GET", + StateArgs, + {params: {state_id: string}}, + RootResponse, + ExecutionOptimisticAndFinalizedMeta >; /** * Get Fork object for requested state * Returns [Fork](https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/beacon-chain.md#fork) object for state with given 'stateId'. - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. */ - getStateFork(stateId: StateId): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: phase0.Fork; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getStateFork: Endpoint< + "GET", + StateArgs, + {params: {state_id: string}}, + phase0.Fork, + ExecutionOptimisticAndFinalizedMeta >; /** * Fetch the RANDAO mix for the requested epoch from the state identified by 'stateId'. - * - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. - * @param epoch Fetch randao mix for the given epoch. If an epoch is not specified then the RANDAO mix for the state's current epoch will be returned. */ - getStateRandao( - stateId: StateId, - epoch?: Epoch - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: {randao: Root}; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getStateRandao: Endpoint< + "GET", + StateArgs & { + /** + * Fetch randao mix for the given epoch. If an epoch is not specified + * then the RANDAO mix for the state's current epoch will be returned. + */ + epoch?: Epoch; + }, + {params: {state_id: string}; query: {epoch?: number}}, + RandaoResponse, + ExecutionOptimisticAndFinalizedMeta >; /** * Get state finality checkpoints * Returns finality checkpoints for state with given 'stateId'. * In case finality is not yet achieved, checkpoint should return epoch 0 and ZERO_HASH as root. - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. */ - getStateFinalityCheckpoints(stateId: StateId): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: FinalityCheckpoints; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getStateFinalityCheckpoints: Endpoint< + "GET", + StateArgs, + {params: {state_id: string}}, + FinalityCheckpoints, + ExecutionOptimisticAndFinalizedMeta >; /** - * Get validators from state - * Returns filterable list of validators with their balance, status and index. - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. - * @param id Either hex encoded public key (with 0x prefix) or validator index - * @param status [Validator status specification](https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ) + * Get validator from state by id + * Returns validator specified by state and id or public key along with status and balance. */ - getStateValidators( - stateId: StateId, - filters?: ValidatorFilters - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: ValidatorResponse[]; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getStateValidator: Endpoint< + "GET", + StateArgs & { + /** Either hex encoded public key (with 0x prefix) or validator index */ + validatorId: ValidatorId; + }, + {params: {state_id: string; validator_id: ValidatorId}}, + ValidatorResponse, + ExecutionOptimisticAndFinalizedMeta >; /** * Get validators from state * Returns filterable list of validators with their balance, status and index. - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. - * @param id Either hex encoded public key (with 0x prefix) or validator index - * @param status [Validator status specification](https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ) */ - postStateValidators( - stateId: StateId, - filters?: ValidatorFilters - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: ValidatorResponse[]; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getStateValidators: Endpoint< + "GET", + StateArgs & { + /** Either hex encoded public key (with 0x prefix) or validator index */ + validatorIds?: ValidatorId[]; + /** [Validator status specification](https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ) */ + statuses?: ValidatorStatus[]; + }, + {params: {state_id: string}; query: {id?: ValidatorId[]; status?: ValidatorStatus[]}}, + ValidatorResponseList, + ExecutionOptimisticAndFinalizedMeta >; /** - * Get validator from state by id - * Returns validator specified by state and id or public key along with status and balance. - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. - * @param validatorId Either hex encoded public key (with 0x prefix) or validator index + * Get validators from state + * Returns filterable list of validators with their balance, status and index. */ - getStateValidator( - stateId: StateId, - validatorId: ValidatorId - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: ValidatorResponse; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + postStateValidators: Endpoint< + "POST", + StateArgs & { + /** Either hex encoded public key (with 0x prefix) or validator index */ + validatorIds?: ValidatorId[]; + /** [Validator status specification](https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ) */ + statuses?: ValidatorStatus[]; + }, + {params: {state_id: string}; body: {ids?: string[]; statuses?: ValidatorStatus[]}}, + ValidatorResponseList, + ExecutionOptimisticAndFinalizedMeta >; /** * Get validator balances from state * Returns filterable list of validator balances. - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. - * @param id Either hex encoded public key (with 0x prefix) or validator index */ - getStateValidatorBalances( - stateId: StateId, - indices?: ValidatorId[] - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: ValidatorBalance[]; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST - > + getStateValidatorBalances: Endpoint< + "GET", + StateArgs & { + /** Either hex encoded public key (with 0x prefix) or validator index */ + validatorIds?: ValidatorId[]; + }, + {params: {state_id: string}; query: {id?: ValidatorId[]}}, + ValidatorBalanceList, + ExecutionOptimisticAndFinalizedMeta >; /** * Get validator balances from state * Returns filterable list of validator balances. - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. - * @param id Either hex encoded public key (with 0x prefix) or validator index */ - postStateValidatorBalances( - stateId: StateId, - indices?: ValidatorId[] - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: ValidatorBalance[]; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST - > + postStateValidatorBalances: Endpoint< + "POST", + StateArgs & { + /** Either hex encoded public key (with 0x prefix) or validator index */ + validatorIds?: ValidatorId[]; + }, + {params: {state_id: string}; body: string[]}, + ValidatorBalanceList, + ExecutionOptimisticAndFinalizedMeta >; /** * Get all committees for a state. * Retrieves the committees for the given state. - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. - * @param epoch Fetch committees for the given epoch. If not present then the committees for the epoch of the state will be obtained. - * @param index Restrict returned values to those matching the supplied committee index. - * @param slot Restrict returned values to those matching the supplied slot. */ - getEpochCommittees( - stateId: StateId, - filters?: CommitteesFilters - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: EpochCommitteeResponse[]; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getEpochCommittees: Endpoint< + "GET", + StateArgs & { + /** Fetch committees for the given epoch. If not present then the committees for the epoch of the state will be obtained. */ + epoch?: Epoch; + /** Restrict returned values to those matching the supplied committee index. */ + index?: CommitteeIndex; + /** Restrict returned values to those matching the supplied slot. */ + slot?: Slot; + }, + {params: {state_id: string}; query: {slot?: number; epoch?: number; index?: number}}, + EpochCommitteeResponseList, + ExecutionOptimisticAndFinalizedMeta >; - getEpochSyncCommittees( - stateId: StateId, - epoch?: Epoch - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: EpochSyncCommitteeResponse; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - }; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getEpochSyncCommittees: Endpoint< + "GET", + StateArgs & {epoch?: Epoch}, + {params: {state_id: string}; query: {epoch?: number}}, + EpochSyncCommitteeResponse, + ExecutionOptimisticAndFinalizedMeta >; }; -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - getEpochCommittees: {url: "/eth/v1/beacon/states/{state_id}/committees", method: "GET"}, - getEpochSyncCommittees: {url: "/eth/v1/beacon/states/{state_id}/sync_committees", method: "GET"}, - getStateFinalityCheckpoints: {url: "/eth/v1/beacon/states/{state_id}/finality_checkpoints", method: "GET"}, - getStateFork: {url: "/eth/v1/beacon/states/{state_id}/fork", method: "GET"}, - getStateRoot: {url: "/eth/v1/beacon/states/{state_id}/root", method: "GET"}, - getStateRandao: {url: "/eth/v1/beacon/states/{state_id}/randao", method: "GET"}, - getStateValidator: {url: "/eth/v1/beacon/states/{state_id}/validators/{validator_id}", method: "GET"}, - getStateValidators: {url: "/eth/v1/beacon/states/{state_id}/validators", method: "GET"}, - postStateValidators: {url: "/eth/v1/beacon/states/{state_id}/validators", method: "POST"}, - getStateValidatorBalances: {url: "/eth/v1/beacon/states/{state_id}/validator_balances", method: "GET"}, - postStateValidatorBalances: {url: "/eth/v1/beacon/states/{state_id}/validator_balances", method: "POST"}, -}; - -/* eslint-disable @typescript-eslint/naming-convention */ - -type StateIdOnlyReq = {params: {state_id: string}}; - -export type ReqTypes = { - getEpochCommittees: {params: {state_id: StateId}; query: {slot?: number; epoch?: number; index?: number}}; - getEpochSyncCommittees: {params: {state_id: StateId}; query: {epoch?: number}}; - getStateFinalityCheckpoints: StateIdOnlyReq; - getStateFork: StateIdOnlyReq; - getStateRoot: StateIdOnlyReq; - getStateRandao: {params: {state_id: StateId}; query: {epoch?: number}}; - getStateValidator: {params: {state_id: StateId; validator_id: ValidatorId}}; - getStateValidators: {params: {state_id: StateId}; query: {id?: ValidatorId[]; status?: ValidatorStatus[]}}; - postStateValidators: {params: {state_id: StateId}; body: {ids?: string[]; statuses?: ValidatorStatus[]}}; - getStateValidatorBalances: {params: {state_id: StateId}; query: {id?: ValidatorId[]}}; - postStateValidatorBalances: {params: {state_id: StateId}; body?: string[]}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const stateIdOnlyReq: RequestCodec> = { + writeReq: ({stateId}) => ({params: {state_id: stateId.toString()}}), + parseReq: ({params}) => ({stateId: params.state_id}), + schema: {params: {state_id: Schema.StringRequired}}, }; -export function getReqSerializers(): ReqSerializers { - const stateIdOnlyReq: ReqSerializer = { - writeReq: (state_id) => ({params: {state_id: String(state_id)}}), - parseReq: ({params}) => [params.state_id], - schema: {params: {state_id: Schema.StringRequired}}, - }; - +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { getEpochCommittees: { - writeReq: (state_id, filters) => ({params: {state_id}, query: filters || {}}), - parseReq: ({params, query}) => [params.state_id, query], - schema: { - params: {state_id: Schema.StringRequired}, - query: {slot: Schema.Uint, epoch: Schema.Uint, index: Schema.Uint}, + url: "/eth/v1/beacon/states/{state_id}/committees", + method: "GET", + req: { + writeReq: ({stateId, epoch, index, slot}) => ({ + params: {state_id: stateId.toString()}, + query: {epoch, index, slot}, + }), + parseReq: ({params, query}) => ({ + stateId: params.state_id, + epoch: query.epoch, + index: query.index, + slot: query.slot, + }), + schema: { + params: {state_id: Schema.StringRequired}, + query: {slot: Schema.Uint, epoch: Schema.Uint, index: Schema.Uint}, + }, + }, + resp: { + data: EpochCommitteeResponseListType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, - getEpochSyncCommittees: { - writeReq: (state_id, epoch) => ({params: {state_id}, query: {epoch}}), - parseReq: ({params, query}) => [params.state_id, query.epoch], - schema: { - params: {state_id: Schema.StringRequired}, - query: {epoch: Schema.Uint}, + url: "/eth/v1/beacon/states/{state_id}/sync_committees", + method: "GET", + req: { + writeReq: ({stateId, epoch}) => ({params: {state_id: stateId.toString()}, query: {epoch}}), + parseReq: ({params, query}) => ({stateId: params.state_id, epoch: query.epoch}), + schema: { + params: {state_id: Schema.StringRequired}, + query: {epoch: Schema.Uint}, + }, + }, + resp: { + data: EpochSyncCommitteeResponseType, + meta: ExecutionOptimisticAndFinalizedCodec, + }, + }, + getStateFinalityCheckpoints: { + url: "/eth/v1/beacon/states/{state_id}/finality_checkpoints", + method: "GET", + req: stateIdOnlyReq, + resp: { + data: FinalityCheckpointsType, + meta: ExecutionOptimisticAndFinalizedCodec, + }, + }, + getStateFork: { + url: "/eth/v1/beacon/states/{state_id}/fork", + method: "GET", + req: stateIdOnlyReq, + resp: { + data: ssz.phase0.Fork, + meta: ExecutionOptimisticAndFinalizedCodec, + }, + }, + getStateRoot: { + url: "/eth/v1/beacon/states/{state_id}/root", + method: "GET", + req: stateIdOnlyReq, + resp: { + data: RootResponseType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, - - getStateFinalityCheckpoints: stateIdOnlyReq, - getStateFork: stateIdOnlyReq, - getStateRoot: stateIdOnlyReq, - getStateRandao: { - writeReq: (state_id, epoch) => ({params: {state_id}, query: {epoch}}), - parseReq: ({params, query}) => [params.state_id, query.epoch], - schema: { - params: {state_id: Schema.StringRequired}, - query: {epoch: Schema.Uint}, + url: "/eth/v1/beacon/states/{state_id}/randao", + method: "GET", + req: { + writeReq: ({stateId, epoch}) => ({params: {state_id: stateId.toString()}, query: {epoch}}), + parseReq: ({params, query}) => ({stateId: params.state_id, epoch: query.epoch}), + schema: { + params: {state_id: Schema.StringRequired}, + query: {epoch: Schema.Uint}, + }, + }, + resp: { + data: RandaoResponseType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, - getStateValidator: { - writeReq: (state_id, validator_id) => ({params: {state_id, validator_id}}), - parseReq: ({params}) => [params.state_id, params.validator_id], - schema: { - params: {state_id: Schema.StringRequired, validator_id: Schema.StringRequired}, + url: "/eth/v1/beacon/states/{state_id}/validators/{validator_id}", + method: "GET", + req: { + writeReq: ({stateId, validatorId}) => ({params: {state_id: stateId.toString(), validator_id: validatorId}}), + parseReq: ({params}) => ({stateId: params.state_id, validatorId: params.validator_id}), + schema: { + params: {state_id: Schema.StringRequired, validator_id: Schema.StringRequired}, + }, + }, + resp: { + onlySupport: WireFormat.json, + data: ValidatorResponseType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, - getStateValidators: { - writeReq: (state_id, filters) => ({params: {state_id}, query: filters || {}}), - parseReq: ({params, query}) => [params.state_id, query], - schema: { - params: {state_id: Schema.StringRequired}, - query: {id: Schema.UintOrStringArray, status: Schema.StringArray}, + url: "/eth/v1/beacon/states/{state_id}/validators", + method: "GET", + req: { + writeReq: ({stateId, validatorIds: id, statuses}) => ({ + params: {state_id: stateId.toString()}, + query: {id, status: statuses}, + }), + parseReq: ({params, query}) => ({stateId: params.state_id, validatorIds: query.id, statuses: query.status}), + schema: { + params: {state_id: Schema.StringRequired}, + query: {id: Schema.UintOrStringArray, status: Schema.StringArray}, + }, + }, + resp: { + onlySupport: WireFormat.json, + data: ValidatorResponseListType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, - postStateValidators: { - writeReq: (state_id, filters) => ({ - params: {state_id}, - body: { - ids: filters?.id?.map((id) => (typeof id === "string" ? id : toU64Str(id))), - statuses: filters?.status, + url: "/eth/v1/beacon/states/{state_id}/validators", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({stateId, validatorIds, statuses}) => ({ + params: {state_id: stateId.toString()}, + body: { + ids: toValidatorIdsStr(validatorIds), + statuses, + }, + }), + parseReqJson: ({params, body = {}}) => ({ + stateId: params.state_id, + validatorIds: fromValidatorIdsStr(body.ids), + statuses: body.statuses, + }), + schema: { + params: {state_id: Schema.StringRequired}, + body: Schema.Object, }, }), - parseReq: ({params, body}) => [ - params.state_id, - { - id: body.ids?.map((id) => (typeof id === "string" && id.startsWith("0x") ? id : fromU64Str(id))), - status: body.statuses, - }, - ], - schema: { - params: {state_id: Schema.StringRequired}, - body: Schema.Object, + resp: { + onlySupport: WireFormat.json, + data: ValidatorResponseListType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, - getStateValidatorBalances: { - writeReq: (state_id, id) => ({params: {state_id}, query: {id}}), - parseReq: ({params, query}) => [params.state_id, query.id], - schema: { - params: {state_id: Schema.StringRequired}, - query: {id: Schema.UintOrStringArray}, + url: "/eth/v1/beacon/states/{state_id}/validator_balances", + method: "GET", + req: { + writeReq: ({stateId, validatorIds}) => ({params: {state_id: stateId.toString()}, query: {id: validatorIds}}), + parseReq: ({params, query}) => ({stateId: params.state_id, validatorIds: query.id}), + schema: { + params: {state_id: Schema.StringRequired}, + query: {id: Schema.UintOrStringArray}, + }, + }, + resp: { + data: ValidatorBalanceListType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, - postStateValidatorBalances: { - writeReq: (state_id, ids) => ({ - params: {state_id}, - body: ids?.map((id) => (typeof id === "string" ? id : toU64Str(id))) || [], + url: "/eth/v1/beacon/states/{state_id}/validator_balances", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({stateId, validatorIds}) => ({ + params: {state_id: stateId.toString()}, + body: toValidatorIdsStr(validatorIds) || [], + }), + parseReqJson: ({params, body = []}) => ({ + stateId: params.state_id, + validatorIds: fromValidatorIdsStr(body), + }), + schema: { + params: {state_id: Schema.StringRequired}, + body: Schema.UintOrStringArray, + }, }), - parseReq: ({params, body}) => [ - params.state_id, - body?.map((id) => (typeof id === "string" && id.startsWith("0x") ? id : fromU64Str(id))), - ], - schema: { - params: {state_id: Schema.StringRequired}, - body: Schema.UintOrStringArray, + resp: { + data: ValidatorBalanceListType, + meta: ExecutionOptimisticAndFinalizedCodec, }, }, }; } - -export function getReturnTypes(): ReturnTypes { - const RootContainer = new ContainerType({ - root: ssz.Root, - }); - - const RandaoContainer = new ContainerType({ - randao: ssz.Root, - }); - - const FinalityCheckpoints = new ContainerType( - { - previousJustified: ssz.phase0.Checkpoint, - currentJustified: ssz.phase0.Checkpoint, - finalized: ssz.phase0.Checkpoint, - }, - {jsonCase: "eth2"} - ); - - const ValidatorResponse = new ContainerType( - { - index: ssz.ValidatorIndex, - balance: ssz.UintNum64, - status: new StringType(), - validator: ssz.phase0.Validator, - }, - {jsonCase: "eth2"} - ); - - const ValidatorBalance = new ContainerType( - { - index: ssz.ValidatorIndex, - balance: ssz.UintNum64, - }, - {jsonCase: "eth2"} - ); - - const EpochCommitteeResponse = new ContainerType( - { - index: ssz.CommitteeIndex, - slot: ssz.Slot, - validators: ssz.phase0.CommitteeIndices, - }, - {jsonCase: "eth2"} - ); - - const EpochSyncCommitteesResponse = new ContainerType( - { - validators: ArrayOf(ssz.ValidatorIndex), - validatorAggregates: ArrayOf(ArrayOf(ssz.ValidatorIndex)), - }, - {jsonCase: "eth2"} - ); - - return { - getStateRoot: WithFinalized(ContainerDataExecutionOptimistic(RootContainer)), - getStateFork: WithFinalized(ContainerDataExecutionOptimistic(ssz.phase0.Fork)), - getStateRandao: WithFinalized(ContainerDataExecutionOptimistic(RandaoContainer)), - getStateFinalityCheckpoints: WithFinalized(ContainerDataExecutionOptimistic(FinalityCheckpoints)), - getStateValidators: WithFinalized(ContainerDataExecutionOptimistic(ArrayOf(ValidatorResponse))), - postStateValidators: WithFinalized(ContainerDataExecutionOptimistic(ArrayOf(ValidatorResponse))), - getStateValidator: WithFinalized(ContainerDataExecutionOptimistic(ValidatorResponse)), - getStateValidatorBalances: WithFinalized(ContainerDataExecutionOptimistic(ArrayOf(ValidatorBalance))), - postStateValidatorBalances: WithFinalized(ContainerDataExecutionOptimistic(ArrayOf(ValidatorBalance))), - getEpochCommittees: WithFinalized(ContainerDataExecutionOptimistic(ArrayOf(EpochCommitteeResponse))), - getEpochSyncCommittees: WithFinalized(ContainerDataExecutionOptimistic(EpochSyncCommitteesResponse)), - }; -} diff --git a/packages/api/src/beacon/routes/config.ts b/packages/api/src/beacon/routes/config.ts index 0d009c844fa2..f8a606af1431 100644 --- a/packages/api/src/beacon/routes/config.ts +++ b/packages/api/src/beacon/routes/config.ts @@ -1,42 +1,60 @@ -import {ByteVectorType, ContainerType} from "@chainsafe/ssz"; -import {BeaconPreset} from "@lodestar/params"; -import {ChainConfig} from "@lodestar/config"; -import {Bytes32, UintNum64, phase0, ssz} from "@lodestar/types"; -import {mapValues} from "@lodestar/utils"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {ContainerType, ValueOf} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; +import {ssz} from "@lodestar/types"; import { ArrayOf, - ReqEmpty, - reqEmpty, - ReturnTypes, - ReqSerializers, - RoutesData, - sameType, - ContainerData, -} from "../../utils/index.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; -import {ApiClientResponse} from "../../interfaces.js"; + EmptyArgs, + EmptyRequestCodec, + EmptyMeta, + EmptyMetaCodec, + EmptyRequest, + JsonOnlyResp, +} from "../../utils/codecs.js"; +import {Endpoint, RouteDefinitions} from "../../utils/index.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -export type DepositContract = { - chainId: UintNum64; - address: Bytes32; -}; +export const DepositContractType = new ContainerType( + { + chainId: ssz.UintNum64, + address: ssz.ExecutionAddress, + }, + {jsonCase: "eth2"} +); -export type Spec = BeaconPreset & ChainConfig; +export const ForkListType = ArrayOf(ssz.phase0.Fork); -export type Api = { +export type DepositContract = ValueOf; +export type ForkList = ValueOf; +export type Spec = Record; + +export type Endpoints = { /** * Get deposit contract address. * Retrieve Eth1 deposit contract address and chain ID. */ - getDepositContract(): Promise>; + getDepositContract: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + DepositContract, + EmptyMeta + >; /** * Get scheduled upcoming forks. * Retrieve all scheduled upcoming forks this node is aware of. */ - getForkSchedule(): Promise>; + getForkSchedule: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + ForkList, + EmptyMeta + >; /** * Retrieve specification configuration used on this node. The configuration should include: @@ -48,37 +66,52 @@ export type Api = { * - any value starting with 0x in the spec is returned as a hex string * - numeric values are returned as a quoted integer */ - getSpec(): Promise}}>>; + getSpec: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + Spec, + EmptyMeta + >; }; -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - getDepositContract: {url: "/eth/v1/config/deposit_contract", method: "GET"}, - getForkSchedule: {url: "/eth/v1/config/fork_schedule", method: "GET"}, - getSpec: {url: "/eth/v1/config/spec", method: "GET"}, -}; - -export type ReqTypes = {[K in keyof Api]: ReqEmpty}; - -export function getReqSerializers(): ReqSerializers { - return mapValues(routesData, () => reqEmpty); -} - -/* eslint-disable @typescript-eslint/naming-convention */ -export function getReturnTypes(): ReturnTypes { - const DepositContract = new ContainerType( - { - chainId: ssz.UintNum64, - address: new ByteVectorType(20), - }, - {jsonCase: "eth2"} - ); - +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { - getDepositContract: ContainerData(DepositContract), - getForkSchedule: ContainerData(ArrayOf(ssz.phase0.Fork)), - getSpec: ContainerData(sameType()), + getDepositContract: { + url: "/eth/v1/config/deposit_contract", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: DepositContractType, + meta: EmptyMetaCodec, + }, + }, + getForkSchedule: { + url: "/eth/v1/config/fork_schedule", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: ForkListType, + meta: EmptyMetaCodec, + }, + }, + getSpec: { + url: "/eth/v1/config/spec", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResp({ + data: { + toJson: (data) => data, + fromJson: (data) => { + if (typeof data !== "object" || data === null) { + throw Error("JSON must be of type object"); + } + return data as Spec; + }, + }, + meta: EmptyMetaCodec, + }), + }, }; } diff --git a/packages/api/src/beacon/routes/debug.ts b/packages/api/src/beacon/routes/debug.ts index 773ae86728b5..2128f7204c6a 100644 --- a/packages/api/src/beacon/routes/debug.ts +++ b/packages/api/src/beacon/routes/debug.ts @@ -1,31 +1,31 @@ -import {ContainerType, ValueOf} from "@chainsafe/ssz"; -import {ForkName} from "@lodestar/params"; -import {allForks, Slot, RootHex, ssz, StringType} from "@lodestar/types"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {ContainerType, Type, ValueOf} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; +import {allForks, ssz, StringType, phase0} from "@lodestar/types"; import { ArrayOf, - ReturnTypes, - RoutesData, - Schema, + EmptyArgs, + EmptyRequestCodec, + EmptyMeta, + EmptyMetaCodec, + EmptyRequest, WithVersion, - TypeJson, - reqEmpty, - ReqSerializers, - ReqEmpty, - ReqSerializer, - ContainerDataExecutionOptimistic, - WithExecutionOptimistic, - WithFinalized, - ContainerData, -} from "../../utils/index.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; -import {parseAcceptHeader, writeAcceptHeader} from "../../utils/acceptHeader.js"; -import {ApiClientResponse, ResponseFormat} from "../../interfaces.js"; -import {ExecutionOptimistic, Finalized, StateId} from "./beacon/state.js"; +} from "../../utils/codecs.js"; +import { + ExecutionOptimisticFinalizedAndVersionCodec, + ExecutionOptimisticFinalizedAndVersionMeta, + ExecutionOptimisticAndFinalizedCodec, + ExecutionOptimisticAndFinalizedMeta, +} from "../../utils/metadata.js"; +import {Endpoint, RouteDefinitions} from "../../utils/types.js"; +import {WireFormat} from "../../utils/wireFormat.js"; +import {Schema} from "../../utils/schema.js"; +import {StateArgs} from "./beacon/state.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes const stringType = new StringType(); -const protoNodeSszType = new ContainerType( +const ProtoNodeResponseType = new ContainerType( { executionPayloadBlockHash: stringType, executionPayloadNumber: ssz.UintNum64, @@ -51,163 +51,164 @@ const protoNodeSszType = new ContainerType( }, {jsonCase: "eth2"} ); +const SlotRootType = new ContainerType( + { + slot: ssz.Slot, + root: stringType, + }, + {jsonCase: "eth2"} +); +const SlotRootExecutionOptimisticType = new ContainerType( + { + slot: ssz.Slot, + root: stringType, + executionOptimistic: ssz.Boolean, + }, + {jsonCase: "eth2"} +); + +const ProtoNodeResponseListType = ArrayOf(ProtoNodeResponseType); +const SlotRootListType = ArrayOf(SlotRootType); +const SlotRootExecutionOptimisticListType = ArrayOf(SlotRootExecutionOptimisticType); -type ProtoNodeApiType = ValueOf; +type ProtoNodeResponseList = ValueOf; +type SlotRootList = ValueOf; +type SlotRootExecutionOptimisticList = ValueOf; -export type Api = { +export type Endpoints = { /** * Retrieves all possible chain heads (leaves of fork choice tree). */ - getDebugChainHeads(): Promise>; + getDebugChainHeads: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + SlotRootList, + EmptyMeta + >; /** * Retrieves all possible chain heads (leaves of fork choice tree). */ - getDebugChainHeadsV2(): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: {data: {slot: Slot; root: RootHex; executionOptimistic: ExecutionOptimistic}[]}; - }> + getDebugChainHeadsV2: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + SlotRootExecutionOptimisticList, + EmptyMeta >; /** * Dump all ProtoArray's nodes to debug */ - getProtoArrayNodes(): Promise>; + getProtoArrayNodes: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + ProtoNodeResponseList, + EmptyMeta + >; /** * Get full BeaconState object * Returns full BeaconState object for given stateId. * Depending on `Accept` header it can be returned either as json or as bytes serialized by SSZ - * - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. */ - getState( - stateId: StateId, - format?: "json" - ): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: {data: allForks.BeaconState; executionOptimistic: ExecutionOptimistic; finalized: Finalized}; - }> - >; - getState(stateId: StateId, format: "ssz"): Promise>; - getState( - stateId: StateId, - format?: ResponseFormat - ): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: - | Uint8Array - | {data: allForks.BeaconState; executionOptimistic: ExecutionOptimistic; finalized: Finalized}; - }> + getState: Endpoint< + "GET", + StateArgs, + {params: {state_id: string}}, + phase0.BeaconState, + ExecutionOptimisticAndFinalizedMeta >; /** * Get full BeaconState object * Returns full BeaconState object for given stateId. * Depending on `Accept` header it can be returned either as json or as bytes serialized by SSZ - * - * @param stateId State identifier. - * Can be one of: "head" (canonical head in node's view), "genesis", "finalized", "justified", \, \. */ - getStateV2( - stateId: StateId, - format?: "json" - ): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: { - data: allForks.BeaconState; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - version: ForkName; - }; - }> - >; - getStateV2(stateId: StateId, format: "ssz"): Promise>; - getStateV2( - stateId: StateId, - format?: ResponseFormat - ): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: - | Uint8Array - | { - data: allForks.BeaconState; - executionOptimistic: ExecutionOptimistic; - finalized: Finalized; - version: ForkName; - }; - }> + getStateV2: Endpoint< + "GET", + StateArgs, + {params: {state_id: string}}, + allForks.BeaconState, + ExecutionOptimisticFinalizedAndVersionMeta >; }; -export const routesData: RoutesData = { - getDebugChainHeads: {url: "/eth/v1/debug/beacon/heads", method: "GET"}, - getDebugChainHeadsV2: {url: "/eth/v2/debug/beacon/heads", method: "GET"}, - getProtoArrayNodes: {url: "/eth/v0/debug/forkchoice", method: "GET"}, - getState: {url: "/eth/v1/debug/beacon/states/{state_id}", method: "GET"}, - getStateV2: {url: "/eth/v2/debug/beacon/states/{state_id}", method: "GET"}, -}; - -/* eslint-disable @typescript-eslint/naming-convention */ - -export type ReqTypes = { - getDebugChainHeads: ReqEmpty; - getDebugChainHeadsV2: ReqEmpty; - getProtoArrayNodes: ReqEmpty; - getState: {params: {state_id: string}; headers: {accept?: string}}; - getStateV2: {params: {state_id: string}; headers: {accept?: string}}; -}; - -export function getReqSerializers(): ReqSerializers { - const getState: ReqSerializer = { - writeReq: (state_id, format) => ({ - params: {state_id: String(state_id)}, - headers: {accept: writeAcceptHeader(format)}, - }), - parseReq: ({params, headers}) => [params.state_id, parseAcceptHeader(headers.accept)], - schema: {params: {state_id: Schema.StringRequired}}, - }; +// Default timeout is not sufficient to download state as JSON +const GET_STATE_TIMEOUT_MS = 5 * 60 * 1000; +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { - getDebugChainHeads: reqEmpty, - getDebugChainHeadsV2: reqEmpty, - getProtoArrayNodes: reqEmpty, - getState: getState, - getStateV2: getState, - }; -} - -export function getReturnTypes(): ReturnTypes { - const SlotRoot = new ContainerType( - { - slot: ssz.Slot, - root: stringType, + getDebugChainHeads: { + url: "/eth/v1/debug/beacon/heads", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: SlotRootListType, + meta: EmptyMetaCodec, + onlySupport: WireFormat.json, + }, }, - {jsonCase: "eth2"} - ); - - const SlotRootExecutionOptimistic = new ContainerType( - { - slot: ssz.Slot, - root: stringType, - executionOptimistic: ssz.Boolean, + getDebugChainHeadsV2: { + url: "/eth/v2/debug/beacon/heads", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: SlotRootExecutionOptimisticListType, + meta: EmptyMetaCodec, + onlySupport: WireFormat.json, + }, + }, + getProtoArrayNodes: { + url: "/eth/v0/debug/forkchoice", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: ProtoNodeResponseListType, + meta: EmptyMetaCodec, + onlySupport: WireFormat.json, + }, + }, + getState: { + url: "/eth/v1/debug/beacon/states/{state_id}", + method: "GET", + req: { + writeReq: ({stateId}) => ({params: {state_id: stateId.toString()}}), + parseReq: ({params}) => ({stateId: params.state_id}), + schema: { + params: {state_id: Schema.StringRequired}, + }, + }, + resp: { + data: ssz.phase0.BeaconState, + meta: ExecutionOptimisticAndFinalizedCodec, + }, + init: { + timeoutMs: GET_STATE_TIMEOUT_MS, + }, + }, + getStateV2: { + url: "/eth/v2/debug/beacon/states/{state_id}", + method: "GET", + req: { + writeReq: ({stateId}) => ({params: {state_id: stateId.toString()}}), + parseReq: ({params}) => ({stateId: params.state_id}), + schema: { + params: {state_id: Schema.StringRequired}, + }, + }, + resp: { + data: WithVersion((fork) => ssz[fork].BeaconState as Type), + meta: ExecutionOptimisticFinalizedAndVersionCodec, + }, + init: { + timeoutMs: GET_STATE_TIMEOUT_MS, + }, }, - {jsonCase: "eth2"} - ); - - return { - getDebugChainHeads: ContainerData(ArrayOf(SlotRoot)), - getDebugChainHeadsV2: ContainerData(ArrayOf(SlotRootExecutionOptimistic)), - getProtoArrayNodes: ContainerData(ArrayOf(protoNodeSszType)), - getState: WithFinalized(ContainerDataExecutionOptimistic(ssz.phase0.BeaconState)), - getStateV2: WithFinalized( - WithExecutionOptimistic( - // Teku returns fork as UPPERCASE - WithVersion( - (fork: ForkName) => ssz[fork.toLowerCase() as ForkName].BeaconState as TypeJson - ) - ) - ), }; } diff --git a/packages/api/src/beacon/routes/events.ts b/packages/api/src/beacon/routes/events.ts index ddcc57104523..0b88175d7588 100644 --- a/packages/api/src/beacon/routes/events.ts +++ b/packages/api/src/beacon/routes/events.ts @@ -1,10 +1,14 @@ import {ContainerType, ValueOf} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; import {Epoch, phase0, capella, Slot, ssz, StringType, RootHex, altair, UintNum64, allForks} from "@lodestar/types"; -import {isForkExecution, ForkName, isForkLightClient} from "@lodestar/params"; +import {ForkName} from "@lodestar/params"; -import {RouteDef, TypeJson, WithVersion} from "../../utils/index.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; -import {ApiClientResponse} from "../../interfaces.js"; +import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; +import {EmptyMeta, EmptyResponseCodec, EmptyResponseData} from "../../utils/codecs.js"; +import {getExecutionForkTypes, getLightClientForkTypes} from "../../utils/fork.js"; +import {VersionType} from "../../utils/metadata.js"; + +// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes const stringType = new StringType(); export const blobSidecarSSE = new ContainerType( @@ -19,8 +23,6 @@ export const blobSidecarSSE = new ContainerType( ); type BlobSidecarSSE = ValueOf; -// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes - export enum EventType { /** * The node has finished processing, resulting in a new head. previous_duty_dependent_root is @@ -119,42 +121,72 @@ export type EventData = { export type BeaconEvent = {[K in EventType]: {type: K; message: EventData[K]}}[EventType]; -export type Api = { +type EventstreamArgs = { + /** Event types to subscribe to */ + topics: EventType[]; + signal: AbortSignal; + onEvent: (event: BeaconEvent) => void; + onError?: (err: Error) => void; + onClose?: () => void; +}; + +export type Endpoints = { /** * Subscribe to beacon node events * Provides endpoint to subscribe to beacon node Server-Sent-Events stream. * Consumers should use [eventsource](https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface) * implementation to listen on those events. * - * @param topics Event types to subscribe to - * @returns Opened SSE stream. + * Returns if SSE stream has been opened. */ - eventstream( - topics: EventType[], - signal: AbortSignal, - onEvent: (event: BeaconEvent) => void - ): Promise>; -}; - -export const routesData: {[K in keyof Api]: RouteDef} = { - eventstream: {url: "/eth/v1/events", method: "GET"}, + eventstream: Endpoint< + // ⏎ + "GET", + EventstreamArgs, + {query: {topics: EventType[]}}, + EmptyResponseData, + EmptyMeta + >; }; -export type ReqTypes = { - eventstream: { - query: {topics: EventType[]}; +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { + return { + eventstream: { + url: "/eth/v1/events", + method: "GET", + req: { + writeReq: ({topics}) => ({query: {topics}}), + parseReq: ({query}) => ({topics: query.topics}) as EventstreamArgs, + schema: { + query: {topics: Schema.StringArrayRequired}, + }, + }, + resp: EmptyResponseCodec, + }, }; -}; +} -// It doesn't make sense to define a getReqSerializers() here given the exotic argument of eventstream() -// The request is very simple: (topics) => {query: {topics}}, and the test will ensure compatibility server - client +export type TypeJson = { + toJson: (data: T) => unknown; // server + fromJson: (data: unknown) => T; // client +}; export function getTypeByEvent(): {[K in EventType]: TypeJson} { - const getLightClientType = (fork: ForkName): allForks.AllForksLightClientSSZTypes => { - if (!isForkLightClient(fork)) { - throw Error(`Invalid fork=${fork} for lightclient fork types`); - } - return ssz.allForksLightClient[fork]; + // eslint-disable-next-line @typescript-eslint/naming-convention + const WithVersion = (getType: (fork: ForkName) => TypeJson): TypeJson<{data: T; version: ForkName}> => { + return { + toJson: ({data, version}) => ({ + data: getType(version).toJson(data), + version, + }), + fromJson: (val) => { + const {version} = VersionType.fromJson(val); + return { + data: getType(version).fromJson((val as {data: unknown}).data), + version, + }; + }, + }; }; return { @@ -211,15 +243,15 @@ export function getTypeByEvent(): {[K in EventType]: TypeJson} { ), [EventType.contributionAndProof]: ssz.altair.SignedContributionAndProof, - [EventType.payloadAttributes]: WithVersion((fork) => - isForkExecution(fork) ? ssz.allForksExecution[fork].SSEPayloadAttributes : ssz.bellatrix.SSEPayloadAttributes - ), + [EventType.payloadAttributes]: WithVersion((fork) => getExecutionForkTypes(fork).SSEPayloadAttributes), [EventType.blobSidecar]: blobSidecarSSE, [EventType.lightClientOptimisticUpdate]: WithVersion( - (fork) => getLightClientType(fork).LightClientOptimisticUpdate + (fork) => getLightClientForkTypes(fork).LightClientOptimisticUpdate + ), + [EventType.lightClientFinalityUpdate]: WithVersion( + (fork) => getLightClientForkTypes(fork).LightClientFinalityUpdate ), - [EventType.lightClientFinalityUpdate]: WithVersion((fork) => getLightClientType(fork).LightClientFinalityUpdate), }; } diff --git a/packages/api/src/beacon/routes/index.ts b/packages/api/src/beacon/routes/index.ts index c3fb15f8a6a4..aef8f3fc4eab 100644 --- a/packages/api/src/beacon/routes/index.ts +++ b/packages/api/src/beacon/routes/index.ts @@ -1,12 +1,12 @@ -import {Api as BeaconApi} from "./beacon/index.js"; -import {Api as ConfigApi} from "./config.js"; -import {Api as DebugApi} from "./debug.js"; -import {Api as EventsApi} from "./events.js"; -import {Api as LightclientApi} from "./lightclient.js"; -import {Api as LodestarApi} from "./lodestar.js"; -import {Api as NodeApi} from "./node.js"; -import {Api as ProofApi} from "./proof.js"; -import {Api as ValidatorApi} from "./validator.js"; +import {Endpoints as BeaconEndpoints} from "./beacon/index.js"; +import {Endpoints as ConfigEndpoints} from "./config.js"; +import {Endpoints as DebugEndpoints} from "./debug.js"; +import {Endpoints as EventsEndpoints} from "./events.js"; +import {Endpoints as LightclientEndpoints} from "./lightclient.js"; +import {Endpoints as LodestarEndpoints} from "./lodestar.js"; +import {Endpoints as NodeEndpoints} from "./node.js"; +import {Endpoints as ProofEndpoints} from "./proof.js"; +import {Endpoints as ValidatorEndpoints} from "./validator.js"; import * as beacon from "./beacon/index.js"; import * as config from "./config.js"; @@ -19,18 +19,19 @@ import * as proof from "./proof.js"; import * as validator from "./validator.js"; export {beacon, config, debug, events, lightclient, lodestar, node, proof, validator}; -export type Api = { - beacon: BeaconApi; - config: ConfigApi; - debug: DebugApi; - events: EventsApi; - lightclient: LightclientApi; - lodestar: LodestarApi; - node: NodeApi; - proof: ProofApi; - validator: ValidatorApi; +export type Endpoints = { + beacon: BeaconEndpoints; + config: ConfigEndpoints; + debug: DebugEndpoints; + events: EventsEndpoints; + lightclient: LightclientEndpoints; + lodestar: LodestarEndpoints; + node: NodeEndpoints; + proof: ProofEndpoints; + validator: ValidatorEndpoints; }; +// TODO: update to reflect new design // Reasoning of the API definitions // ================================ // diff --git a/packages/api/src/beacon/routes/lightclient.ts b/packages/api/src/beacon/routes/lightclient.ts index bbd7e9ff3864..ed5e290a747c 100644 --- a/packages/api/src/beacon/routes/lightclient.ts +++ b/packages/api/src/beacon/routes/lightclient.ts @@ -1,22 +1,27 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {ListCompositeType, ValueOf} from "@chainsafe/ssz"; import {ssz, SyncPeriod, allForks} from "@lodestar/types"; -import {ForkName, isForkLightClient} from "@lodestar/params"; +import {ForkName} from "@lodestar/params"; +import {ChainForkConfig} from "@lodestar/config"; +import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; +import {VersionCodec, VersionMeta} from "../../utils/metadata.js"; +import {getLightClientForkTypes, toForkName} from "../../utils/fork.js"; import { - ArrayOf, - ReturnTypes, - RoutesData, - Schema, - ReqSerializers, - reqEmpty, - ReqEmpty, + EmptyArgs, + EmptyRequestCodec, + EmptyMeta, + EmptyMetaCodec, + EmptyRequest, WithVersion, - ContainerData, -} from "../../utils/index.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; -import {ApiClientResponse} from "../../interfaces.js"; + JsonOnlyResp, +} from "../../utils/codecs.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -export type Api = { +export const HashListType = new ListCompositeType(ssz.Root, 10000); +export type HashList = ValueOf; + +export type Endpoints = { /** * Returns an array of best updates given a `startPeriod` and `count` number of sync committee period to return. * Best is defined by (in order of priority): @@ -24,135 +29,163 @@ export type Api = { * - Has most bits * - Oldest update */ - getUpdates( - startPeriod: SyncPeriod, - count: number - ): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: { - version: ForkName; - data: allForks.LightClientUpdate; - }[]; - }> + getLightClientUpdatesByRange: Endpoint< + "GET", + {startPeriod: SyncPeriod; count: number}, + {query: {start_period: number; count: number}}, + allForks.LightClientUpdate[], + {versions: ForkName[]} >; + /** * Returns the latest optimistic head update available. Clients should use the SSE type `light_client_optimistic_update` * unless to get the very first head update after syncing, or if SSE are not supported by the server. */ - getOptimisticUpdate(): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: { - version: ForkName; - data: allForks.LightClientOptimisticUpdate; - }; - }> + getLightClientOptimisticUpdate: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + allForks.LightClientOptimisticUpdate, + VersionMeta >; - getFinalityUpdate(): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: { - version: ForkName; - data: allForks.LightClientFinalityUpdate; - }; - }> + + getLightClientFinalityUpdate: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + allForks.LightClientFinalityUpdate, + VersionMeta >; + /** * Fetch a bootstrapping state with a proof to a trusted block root. * The trusted block root should be fetched with similar means to a weak subjectivity checkpoint. * Only block roots for checkpoints are guaranteed to be available. */ - getBootstrap(blockRoot: string): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: { - version: ForkName; - data: allForks.LightClientBootstrap; - }; - }> + getLightClientBootstrap: Endpoint< + "GET", + {blockRoot: string}, + {params: {block_root: string}}, + allForks.LightClientBootstrap, + VersionMeta >; + /** * Returns an array of sync committee hashes based on the provided period and count */ - getCommitteeRoot( - startPeriod: SyncPeriod, - count: number - ): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: { - data: Uint8Array[]; - }; - }> + getLightClientCommitteeRoot: Endpoint< + "GET", + {startPeriod: SyncPeriod; count: number}, + {query: {start_period: number; count: number}}, + HashList, + EmptyMeta >; }; -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - getUpdates: {url: "/eth/v1/beacon/light_client/updates", method: "GET"}, - getOptimisticUpdate: {url: "/eth/v1/beacon/light_client/optimistic_update", method: "GET"}, - getFinalityUpdate: {url: "/eth/v1/beacon/light_client/finality_update", method: "GET"}, - getBootstrap: {url: "/eth/v1/beacon/light_client/bootstrap/{block_root}", method: "GET"}, - getCommitteeRoot: {url: "/eth/v0/beacon/light_client/committee_root", method: "GET"}, -}; - -/* eslint-disable @typescript-eslint/naming-convention */ -export type ReqTypes = { - getUpdates: {query: {start_period: number; count: number}}; - getOptimisticUpdate: ReqEmpty; - getFinalityUpdate: ReqEmpty; - getBootstrap: {params: {block_root: string}}; - getCommitteeRoot: {query: {start_period: number; count: number}}; -}; - -export function getReqSerializers(): ReqSerializers { +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { - getUpdates: { - writeReq: (start_period, count) => ({query: {start_period, count}}), - parseReq: ({query}) => [query.start_period, query.count], - schema: {query: {start_period: Schema.UintRequired, count: Schema.UintRequired}}, + getLightClientUpdatesByRange: { + url: "/eth/v1/beacon/light_client/updates", + method: "GET", + req: { + writeReq: ({startPeriod, count}) => ({query: {start_period: startPeriod, count}}), + parseReq: ({query}) => ({startPeriod: query.start_period, count: query.count}), + schema: {query: {start_period: Schema.UintRequired, count: Schema.UintRequired}}, + }, + resp: JsonOnlyResp({ + data: { + toJson: (data, meta) => { + const json: unknown[] = []; + for (const [i, update] of data.entries()) { + json.push(getLightClientForkTypes(meta.versions[i]).LightClientUpdate.toJson(update)); + } + return json; + }, + fromJson: (data, meta) => { + const updates = data as unknown[]; + const value: allForks.LightClientUpdate[] = []; + for (let i = 0; i < updates.length; i++) { + const version = meta.versions[i]; + value.push(getLightClientForkTypes(version).LightClientUpdate.fromJson(updates[i])); + } + return value; + }, + }, + meta: { + toJson: (meta) => meta, + fromJson: (val) => val as {versions: ForkName[]}, + toHeadersObject: () => ({}), + fromHeaders: () => ({versions: []}), + }, + transform: { + toResponse: (data, meta) => { + const updates = data as unknown[]; + const resp: unknown[] = []; + for (let i = 0; i < updates.length; i++) { + resp.push({data: updates[i], version: (meta as {versions: string[]}).versions[i]}); + } + return resp; + }, + fromResponse: (resp) => { + if (!Array.isArray(resp)) { + throw Error("JSON is not an array"); + } + const updates: allForks.LightClientUpdate[] = []; + const meta: {versions: ForkName[]} = {versions: []}; + for (const {data, version} of resp as {data: allForks.LightClientUpdate; version: string}[]) { + updates.push(data); + meta.versions.push(toForkName(version)); + } + return {data: updates, meta}; + }, + }, + }), }, - - getOptimisticUpdate: reqEmpty, - getFinalityUpdate: reqEmpty, - - getBootstrap: { - writeReq: (block_root) => ({params: {block_root}}), - parseReq: ({params}) => [params.block_root], - schema: {params: {block_root: Schema.StringRequired}}, + getLightClientOptimisticUpdate: { + url: "/eth/v1/beacon/light_client/optimistic_update", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: WithVersion((fork) => getLightClientForkTypes(fork).LightClientOptimisticUpdate), + meta: VersionCodec, + }, }, - getCommitteeRoot: { - writeReq: (start_period, count) => ({query: {start_period, count}}), - parseReq: ({query}) => [query.start_period, query.count], - schema: {query: {start_period: Schema.UintRequired, count: Schema.UintRequired}}, + getLightClientFinalityUpdate: { + url: "/eth/v1/beacon/light_client/finality_update", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: WithVersion((fork) => getLightClientForkTypes(fork).LightClientFinalityUpdate), + meta: VersionCodec, + }, + }, + getLightClientBootstrap: { + url: "/eth/v1/beacon/light_client/bootstrap/{block_root}", + method: "GET", + req: { + writeReq: ({blockRoot}) => ({params: {block_root: blockRoot}}), + parseReq: ({params}) => ({blockRoot: params.block_root}), + schema: {params: {block_root: Schema.StringRequired}}, + }, + resp: { + data: WithVersion((fork) => getLightClientForkTypes(fork).LightClientBootstrap), + meta: VersionCodec, + }, + }, + getLightClientCommitteeRoot: { + url: "/eth/v0/beacon/light_client/committee_root", + method: "GET", + req: { + writeReq: ({startPeriod, count}) => ({query: {start_period: startPeriod, count}}), + parseReq: ({query}) => ({startPeriod: query.start_period, count: query.count}), + schema: {query: {start_period: Schema.UintRequired, count: Schema.UintRequired}}, + }, + resp: { + data: HashListType, + meta: EmptyMetaCodec, + }, }, - }; -} - -export function getReturnTypes(): ReturnTypes { - // Form a TypeJson convertor for getUpdates - const VersionedUpdate = WithVersion((fork: ForkName) => - isForkLightClient(fork) ? ssz.allForksLightClient[fork].LightClientUpdate : ssz.altair.LightClientUpdate - ); - const getUpdates = { - toJson: (updates: {version: ForkName; data: allForks.LightClientUpdate}[]) => - updates.map((data) => VersionedUpdate.toJson(data)), - fromJson: (updates: unknown[]) => updates.map((data) => VersionedUpdate.fromJson(data)), - }; - - return { - getUpdates, - getOptimisticUpdate: WithVersion((fork: ForkName) => - isForkLightClient(fork) - ? ssz.allForksLightClient[fork].LightClientOptimisticUpdate - : ssz.altair.LightClientOptimisticUpdate - ), - getFinalityUpdate: WithVersion((fork: ForkName) => - isForkLightClient(fork) - ? ssz.allForksLightClient[fork].LightClientFinalityUpdate - : ssz.altair.LightClientFinalityUpdate - ), - getBootstrap: WithVersion((fork: ForkName) => - isForkLightClient(fork) ? ssz.allForksLightClient[fork].LightClientBootstrap : ssz.altair.LightClientBootstrap - ), - getCommitteeRoot: ContainerData(ArrayOf(ssz.Root)), }; } diff --git a/packages/api/src/beacon/routes/lodestar.ts b/packages/api/src/beacon/routes/lodestar.ts index 527199d95dad..a97d065cfe4d 100644 --- a/packages/api/src/beacon/routes/lodestar.ts +++ b/packages/api/src/beacon/routes/lodestar.ts @@ -1,16 +1,15 @@ +import {ChainForkConfig} from "@lodestar/config"; import {Epoch, RootHex, Slot} from "@lodestar/types"; -import {ApiClientResponse} from "../../interfaces.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; +import {Schema, Endpoint, RouteDefinitions} from "../../utils/index.js"; import { - jsonType, - ReqEmpty, - reqEmpty, - ReturnTypes, - ReqSerializers, - RoutesData, - sameType, - Schema, -} from "../../utils/index.js"; + EmptyArgs, + EmptyRequestCodec, + EmptyMeta, + EmptyRequest, + EmptyResponseCodec, + EmptyResponseData, + JsonOnlyResponseCodec, +} from "../../utils/codecs.js"; import {FilterGetPeers, NodePeer, PeerDirection, PeerState} from "./node.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -76,172 +75,317 @@ export type LodestarNodePeer = NodePeer & { export type LodestarThreadType = "main" | "network" | "discv5"; -export type Api = { - /** Trigger to write a heapdump of either main/network/discv5 thread to disk at `dirpath`. May take > 1min */ - writeHeapdump( - thread?: LodestarThreadType, - dirpath?: string - ): Promise>; - /** Trigger to write profile of either main/network/discv5 thread to disk */ - writeProfile( - thread?: LodestarThreadType, - duration?: number, - dirpath?: string - ): Promise>; +export type Endpoints = { + /** Trigger to write a heapdump to disk at `dirpath`. May take > 1min */ + writeHeapdump: Endpoint< + "POST", + {thread?: LodestarThreadType; dirpath?: string}, + {query: {thread?: LodestarThreadType; dirpath?: string}}, + {filepath: string}, + EmptyMeta + >; + /** Trigger to write 10m network thread profile to disk */ + writeProfile: Endpoint< + "POST", + { + thread?: LodestarThreadType; + duration?: number; + dirpath?: string; + }, + {query: {thread?: LodestarThreadType; duration?: number; dirpath?: string}}, + {filepath: string}, + EmptyMeta + >; /** TODO: description */ - getLatestWeakSubjectivityCheckpointEpoch(): Promise>; + getLatestWeakSubjectivityCheckpointEpoch: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + Epoch, + EmptyMeta + >; /** TODO: description */ - getSyncChainsDebugState(): Promise>; + getSyncChainsDebugState: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + SyncChainDebugState[], + EmptyMeta + >; /** Dump all items in a gossip queue, by gossipType */ - getGossipQueueItems(gossipType: string): Promise>; + getGossipQueueItems: Endpoint< + // ⏎ + "GET", + {gossipType: string}, + {params: {gossipType: string}}, + unknown[], + EmptyMeta + >; /** Dump all items in the regen queue */ - getRegenQueueItems(): Promise>; + getRegenQueueItems: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + RegenQueueItem[], + EmptyMeta + >; /** Dump all items in the block processor queue */ - getBlockProcessorQueueItems(): Promise>; + getBlockProcessorQueueItems: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + BlockProcessorQueueItem[], + EmptyMeta + >; /** Dump a summary of the states in the block state cache and checkpoint state cache */ - getStateCacheItems(): Promise>; + getStateCacheItems: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + StateCacheItem[], + EmptyMeta + >; /** Dump peer gossip stats by peer */ - getGossipPeerScoreStats(): Promise>; + getGossipPeerScoreStats: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + GossipPeerScoreStat[], + EmptyMeta + >; /** Dump lodestar score stats by peer */ - getLodestarPeerScoreStats(): Promise>; + getLodestarPeerScoreStats: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + PeerScoreStat[], + EmptyMeta + >; /** Run GC with `global.gc()` */ - runGC(): Promise>; + runGC: Endpoint< + // ⏎ + "POST", + EmptyArgs, + EmptyRequest, + EmptyResponseData, + EmptyMeta + >; /** Drop all states in the state cache */ - dropStateCache(): Promise>; + dropStateCache: Endpoint< + // ⏎ + "POST", + EmptyArgs, + EmptyRequest, + EmptyResponseData, + EmptyMeta + >; /** Connect to peer at this multiaddress */ - connectPeer(peerId: string, multiaddrStrs: string[]): Promise>; + connectPeer: Endpoint< + // ⏎ + "POST", + {peerId: string; multiaddrs: string[]}, + {query: {peerId: string; multiaddr: string[]}}, + EmptyResponseData, + EmptyMeta + >; /** Disconnect peer */ - disconnectPeer(peerId: string): Promise>; + disconnectPeer: Endpoint< + // ⏎ + "POST", + {peerId: string}, + {query: {peerId: string}}, + EmptyResponseData, + EmptyMeta + >; /** Same to node api with new fields */ - getPeers( - filters?: FilterGetPeers - ): Promise>; + getPeers: Endpoint< + "GET", + FilterGetPeers, + {query: {state?: PeerState[]; direction?: PeerDirection[]}}, + LodestarNodePeer[], + {count: number} + >; /** Dump Discv5 Kad values */ - discv5GetKadValues(): Promise>; + discv5GetKadValues: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + string[], + EmptyMeta + >; /** * Dump level-db entry keys for a given Bucket declared in code, or for all buckets. - * @param bucket must be the string name of a bucket entry: `allForks_blockArchive` */ - dumpDbBucketKeys(bucket: string): Promise>; + dumpDbBucketKeys: Endpoint< + "GET", + { + /** Must be the string name of a bucket entry: `allForks_blockArchive` */ + bucket: string; + }, + {params: {bucket: string}}, + string[], + EmptyMeta + >; /** Return all entries in the StateArchive index with bucket index_stateArchiveRootIndex */ - dumpDbStateIndex(): Promise>; -}; - -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - writeHeapdump: {url: "/eth/v1/lodestar/write_heapdump", method: "POST"}, - writeProfile: {url: "/eth/v1/lodestar/write_profile", method: "POST"}, - getLatestWeakSubjectivityCheckpointEpoch: {url: "/eth/v1/lodestar/ws_epoch", method: "GET"}, - getSyncChainsDebugState: {url: "/eth/v1/lodestar/sync_chains_debug_state", method: "GET"}, - getGossipQueueItems: {url: "/eth/v1/lodestar/gossip_queue_items/:gossipType", method: "GET"}, - getRegenQueueItems: {url: "/eth/v1/lodestar/regen_queue_items", method: "GET"}, - getBlockProcessorQueueItems: {url: "/eth/v1/lodestar/block_processor_queue_items", method: "GET"}, - getStateCacheItems: {url: "/eth/v1/lodestar/state_cache_items", method: "GET"}, - getGossipPeerScoreStats: {url: "/eth/v1/lodestar/gossip_peer_score_stats", method: "GET"}, - getLodestarPeerScoreStats: {url: "/eth/v1/lodestar/lodestar_peer_score_stats", method: "GET"}, - runGC: {url: "/eth/v1/lodestar/gc", method: "POST"}, - dropStateCache: {url: "/eth/v1/lodestar/drop_state_cache", method: "POST"}, - connectPeer: {url: "/eth/v1/lodestar/connect_peer", method: "POST"}, - disconnectPeer: {url: "/eth/v1/lodestar/disconnect_peer", method: "POST"}, - getPeers: {url: "/eth/v1/lodestar/peers", method: "GET"}, - discv5GetKadValues: {url: "/eth/v1/debug/discv5_kad_values", method: "GET"}, - dumpDbBucketKeys: {url: "/eth/v1/debug/dump_db_bucket_keys/:bucket", method: "GET"}, - dumpDbStateIndex: {url: "/eth/v1/debug/dump_db_state_index", method: "GET"}, -}; - -export type ReqTypes = { - writeHeapdump: {query: {thread?: LodestarThreadType; dirpath?: string}}; - writeProfile: {query: {thread?: LodestarThreadType; duration?: number; dirpath?: string}}; - getLatestWeakSubjectivityCheckpointEpoch: ReqEmpty; - getSyncChainsDebugState: ReqEmpty; - getGossipQueueItems: {params: {gossipType: string}}; - getRegenQueueItems: ReqEmpty; - getBlockProcessorQueueItems: ReqEmpty; - getStateCacheItems: ReqEmpty; - getGossipPeerScoreStats: ReqEmpty; - getLodestarPeerScoreStats: ReqEmpty; - runGC: ReqEmpty; - dropStateCache: ReqEmpty; - connectPeer: {query: {peerId: string; multiaddr: string[]}}; - disconnectPeer: {query: {peerId: string}}; - getPeers: {query: {state?: PeerState[]; direction?: PeerDirection[]}}; - discv5GetKadValues: ReqEmpty; - dumpDbBucketKeys: {params: {bucket: string}}; - dumpDbStateIndex: ReqEmpty; + dumpDbStateIndex: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + {root: RootHex; slot: Slot}[], + EmptyMeta + >; }; -export function getReqSerializers(): ReqSerializers { +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { writeHeapdump: { - writeReq: (thread, dirpath) => ({query: {thread, dirpath}}), - parseReq: ({query}) => [query.thread, query.dirpath], - schema: {query: {dirpath: Schema.String}}, + url: "/eth/v1/lodestar/write_heapdump", + method: "POST", + req: { + writeReq: ({thread, dirpath}) => ({query: {thread, dirpath}}), + parseReq: ({query}) => ({thread: query.thread, dirpath: query.dirpath}), + schema: {query: {thread: Schema.String, dirpath: Schema.String}}, + }, + resp: JsonOnlyResponseCodec, }, writeProfile: { - writeReq: (thread, duration, dirpath) => ({query: {thread, duration, dirpath}}), - parseReq: ({query}) => [query.thread, query.duration, query.dirpath], - schema: {query: {dirpath: Schema.String}}, + url: "/eth/v1/lodestar/write_profile", + method: "POST", + req: { + writeReq: ({thread, duration, dirpath}) => ({query: {thread, duration, dirpath}}), + parseReq: ({query}) => ({thread: query.thread, duration: query.duration, dirpath: query.dirpath}), + schema: {query: {thread: Schema.String, duration: Schema.Uint, dirpath: Schema.String}}, + }, + resp: JsonOnlyResponseCodec, + }, + getLatestWeakSubjectivityCheckpointEpoch: { + url: "/eth/v1/lodestar/ws_epoch", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + }, + getSyncChainsDebugState: { + url: "/eth/v1/lodestar/sync_chains_debug_state", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, }, - getLatestWeakSubjectivityCheckpointEpoch: reqEmpty, - getSyncChainsDebugState: reqEmpty, getGossipQueueItems: { - writeReq: (gossipType) => ({params: {gossipType}}), - parseReq: ({params}) => [params.gossipType], - schema: {params: {gossipType: Schema.StringRequired}}, - }, - getRegenQueueItems: reqEmpty, - getBlockProcessorQueueItems: reqEmpty, - getStateCacheItems: reqEmpty, - getGossipPeerScoreStats: reqEmpty, - getLodestarPeerScoreStats: reqEmpty, - runGC: reqEmpty, - dropStateCache: reqEmpty, + url: "/eth/v1/lodestar/gossip_queue_items/:gossipType", + method: "GET", + req: { + writeReq: ({gossipType}) => ({params: {gossipType}}), + parseReq: ({params}) => ({gossipType: params.gossipType}), + schema: {params: {gossipType: Schema.StringRequired}}, + }, + resp: JsonOnlyResponseCodec, + }, + getRegenQueueItems: { + url: "/eth/v1/lodestar/regen_queue_items", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + }, + getBlockProcessorQueueItems: { + url: "/eth/v1/lodestar/block_processor_queue_items", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + }, + getStateCacheItems: { + url: "/eth/v1/lodestar/state_cache_items", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + }, + getGossipPeerScoreStats: { + url: "/eth/v1/lodestar/gossip_peer_score_stats", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + }, + getLodestarPeerScoreStats: { + url: "/eth/v1/lodestar/lodestar_peer_score_stats", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + }, + runGC: { + url: "/eth/v1/lodestar/gc", + method: "POST", + req: EmptyRequestCodec, + resp: EmptyResponseCodec, + }, + dropStateCache: { + url: "/eth/v1/lodestar/drop_state_cache", + method: "POST", + req: EmptyRequestCodec, + resp: EmptyResponseCodec, + }, connectPeer: { - writeReq: (peerId, multiaddr) => ({query: {peerId, multiaddr}}), - parseReq: ({query}) => [query.peerId, query.multiaddr], - schema: {query: {peerId: Schema.StringRequired, multiaddr: Schema.StringArray}}, + url: "/eth/v1/lodestar/connect_peer", + method: "POST", + req: { + writeReq: ({peerId, multiaddrs}) => ({query: {peerId, multiaddr: multiaddrs}}), + parseReq: ({query}) => ({peerId: query.peerId, multiaddrs: query.multiaddr}), + schema: {query: {peerId: Schema.StringRequired, multiaddr: Schema.StringArray}}, + }, + resp: EmptyResponseCodec, }, disconnectPeer: { - writeReq: (peerId) => ({query: {peerId}}), - parseReq: ({query}) => [query.peerId], - schema: {query: {peerId: Schema.StringRequired}}, + url: "/eth/v1/lodestar/disconnect_peer", + method: "POST", + req: { + writeReq: ({peerId}) => ({query: {peerId}}), + parseReq: ({query}) => ({peerId: query.peerId}), + schema: {query: {peerId: Schema.StringRequired}}, + }, + resp: EmptyResponseCodec, }, getPeers: { - writeReq: (filters) => ({query: filters || {}}), - parseReq: ({query}) => [query], - schema: {query: {state: Schema.StringArray, direction: Schema.StringArray}}, + url: "/eth/v1/lodestar/peers", + method: "GET", + req: { + writeReq: ({state, direction}) => ({query: {state, direction}}), + parseReq: ({query}) => ({state: query.state, direction: query.direction}), + schema: {query: {state: Schema.StringArray, direction: Schema.StringArray}}, + }, + resp: JsonOnlyResponseCodec, + }, + discv5GetKadValues: { + url: "/eth/v1/debug/discv5_kad_values", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, }, - discv5GetKadValues: reqEmpty, dumpDbBucketKeys: { - writeReq: (bucket) => ({params: {bucket}}), - parseReq: ({params}) => [params.bucket], - schema: {params: {bucket: Schema.String}}, + url: "/eth/v1/debug/dump_db_bucket_keys/:bucket", + method: "GET", + req: { + writeReq: ({bucket}) => ({params: {bucket}}), + parseReq: ({params}) => ({bucket: params.bucket}), + schema: {params: {bucket: Schema.String}}, + }, + resp: JsonOnlyResponseCodec, + }, + dumpDbStateIndex: { + url: "/eth/v1/debug/dump_db_state_index", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, }, - dumpDbStateIndex: reqEmpty, - }; -} - -export function getReturnTypes(): ReturnTypes { - return { - writeHeapdump: sameType(), - writeProfile: sameType(), - getLatestWeakSubjectivityCheckpointEpoch: sameType(), - getSyncChainsDebugState: jsonType("snake"), - getGossipQueueItems: jsonType("snake"), - getRegenQueueItems: jsonType("snake"), - getBlockProcessorQueueItems: jsonType("snake"), - getStateCacheItems: jsonType("snake"), - getGossipPeerScoreStats: jsonType("snake"), - getLodestarPeerScoreStats: jsonType("snake"), - getPeers: jsonType("snake"), - discv5GetKadValues: jsonType("snake"), - dumpDbBucketKeys: sameType(), - dumpDbStateIndex: sameType(), }; } diff --git a/packages/api/src/beacon/routes/node.ts b/packages/api/src/beacon/routes/node.ts index 9a954fe6ad57..1ff0378c3330 100644 --- a/packages/api/src/beacon/routes/node.ts +++ b/packages/api/src/beacon/routes/node.ts @@ -1,31 +1,49 @@ -import {ContainerType} from "@chainsafe/ssz"; -import {allForks, ssz, StringType} from "@lodestar/types"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {ContainerType, ValueOf} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; +import {ssz, stringType} from "@lodestar/types"; +import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; import { ArrayOf, - reqEmpty, - jsonType, - ReturnTypes, - RoutesData, - Schema, - ReqSerializers, - ReqEmpty, - ContainerData, -} from "../../utils/index.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; -import {ApiClientResponse} from "../../interfaces.js"; + EmptyArgs, + EmptyRequestCodec, + EmptyMeta, + EmptyMetaCodec, + EmptyRequest, + EmptyResponseCodec, + EmptyResponseData, + JsonOnlyResponseCodec, +} from "../../utils/codecs.js"; +import {HttpStatusCode} from "../../utils/httpStatusCode.js"; +import {WireFormat} from "../../utils/wireFormat.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -export type NetworkIdentity = { - /** Cryptographic hash of a peer’s public key. [Read more](https://docs.libp2p.io/concepts/peer-id/) */ - peerId: string; - /** Ethereum node record. [Read more](https://eips.ethereum.org/EIPS/eip-778) */ - enr: string; - p2pAddresses: string[]; - discoveryAddresses: string[]; - /** Based on Ethereum Consensus [Metadata object](https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#metadata) */ - metadata: allForks.Metadata; -}; +export const NetworkIdentityType = new ContainerType( + { + /** Cryptographic hash of a peer’s public key. [Read more](https://docs.libp2p.io/concepts/peer-id/) */ + peerId: stringType, + /** Ethereum node record. [Read more](https://eips.ethereum.org/EIPS/eip-778) */ + enr: stringType, + p2pAddresses: ArrayOf(stringType), + discoveryAddresses: ArrayOf(stringType), + /** Based on Ethereum Consensus [Metadata object](https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md#metadata) */ + metadata: ssz.altair.Metadata, + }, + {jsonCase: "eth2"} +); + +export const PeerCountType = new ContainerType( + { + disconnected: ssz.UintNum64, + connecting: ssz.UintNum64, + connected: ssz.UintNum64, + disconnecting: ssz.UintNum64, + }, + {jsonCase: "eth2"} +); + +export type NetworkIdentity = ValueOf; export type PeerState = "disconnected" | "connecting" | "connected" | "disconnecting"; export type PeerDirection = "inbound" | "outbound"; @@ -39,12 +57,9 @@ export type NodePeer = { direction: PeerDirection | null; }; -export type PeerCount = { - disconnected: number; - connecting: number; - connected: number; - disconnecting: number; -}; +export type PeersMeta = {count: number}; + +export type PeerCount = ValueOf; export type FilterGetPeers = { state?: PeerState[]; @@ -70,155 +85,175 @@ export enum NodeHealth { NOT_INITIALIZED_OR_ISSUES = HttpStatusCode.SERVICE_UNAVAILABLE, } -export type NodeHealthOptions = { - syncingStatus?: number; -}; - /** * Read information about the beacon node. */ -export type Api = { +export type Endpoints = { /** * Get node network identity * Retrieves data about the node's network presence */ - getNetworkIdentity: () => Promise>; + getNetworkIdentity: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + NetworkIdentity, + EmptyMeta + >; + /** * Get node network peers * Retrieves data about the node's network peers. By default this returns all peers. Multiple query params are combined using AND conditions - * @param state - * @param direction */ - getPeers( - filters?: FilterGetPeers - ): Promise>; + getPeers: Endpoint< + "GET", + FilterGetPeers, + {query: {state?: PeerState[]; direction?: PeerDirection[]}}, + NodePeer[], + PeersMeta + >; + /** * Get peer * Retrieves data about the given peer - * @param peerId */ - getPeer( - peerId: string - ): Promise< - ApiClientResponse<{[HttpStatusCode.OK]: {data: NodePeer}}, HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND> + getPeer: Endpoint< + // ⏎ + "GET", + {peerId: string}, + {params: {peer_id: string}}, + NodePeer, + EmptyMeta >; /** * Get peer count * Retrieves number of known peers. */ - getPeerCount(): Promise>; + getPeerCount: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + PeerCount, + EmptyMeta + >; /** * Get version string of the running beacon node. * Requests that the beacon node identify information about its implementation in a format similar to a [HTTP User-Agent](https://tools.ietf.org/html/rfc7231#section-5.5.3) field. */ - getNodeVersion(): Promise>; + getNodeVersion: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + {version: string}, + EmptyMeta + >; /** * Get node syncing status * Requests the beacon node to describe if it's currently syncing or not, and if it is, what block it is up to. */ - getSyncingStatus(): Promise>; + getSyncingStatus: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + SyncingStatus, + EmptyMeta + >; /** * Get health check * Returns node health status in http status codes. Useful for load balancers. */ - getHealth( - options?: NodeHealthOptions - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: void; [HttpStatusCode.PARTIAL_CONTENT]: void}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + getHealth: Endpoint< + // ⏎ + "GET", + {syncingStatus?: number}, + {query: {syncing_status?: number}}, + EmptyResponseData, + EmptyMeta >; }; -export const routesData: RoutesData = { - getNetworkIdentity: {url: "/eth/v1/node/identity", method: "GET"}, - getPeers: {url: "/eth/v1/node/peers", method: "GET"}, - getPeer: {url: "/eth/v1/node/peers/{peer_id}", method: "GET"}, - getPeerCount: {url: "/eth/v1/node/peer_count", method: "GET"}, - getNodeVersion: {url: "/eth/v1/node/version", method: "GET"}, - getSyncingStatus: {url: "/eth/v1/node/syncing", method: "GET"}, - getHealth: {url: "/eth/v1/node/health", method: "GET"}, -}; - -/* eslint-disable @typescript-eslint/naming-convention */ - -export type ReqTypes = { - getNetworkIdentity: ReqEmpty; - getPeers: {query: {state?: PeerState[]; direction?: PeerDirection[]}}; - getPeer: {params: {peer_id: string}}; - getPeerCount: ReqEmpty; - getNodeVersion: ReqEmpty; - getSyncingStatus: ReqEmpty; - getHealth: {query: {syncing_status?: number}}; -}; - -export function getReqSerializers(): ReqSerializers { +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { - getNetworkIdentity: reqEmpty, - + getNetworkIdentity: { + url: "/eth/v1/node/identity", + method: "GET", + req: EmptyRequestCodec, + resp: { + onlySupport: WireFormat.json, + data: NetworkIdentityType, + meta: EmptyMetaCodec, + }, + }, getPeers: { - writeReq: (filters) => ({query: filters || {}}), - parseReq: ({query}) => [query], - schema: {query: {state: Schema.StringArray, direction: Schema.StringArray}}, + url: "/eth/v1/node/peers", + method: "GET", + req: { + writeReq: ({state, direction}) => ({query: {state, direction}}), + parseReq: ({query}) => ({state: query.state, direction: query.direction}), + schema: {query: {state: Schema.StringArray, direction: Schema.StringArray}}, + }, + resp: { + ...JsonOnlyResponseCodec, + meta: { + toJson: (d) => d, + fromJson: (d) => ({count: (d as PeersMeta).count}), + toHeadersObject: () => ({}), + fromHeaders: () => ({}) as PeersMeta, + }, + transform: { + toResponse: (data, meta) => ({data, meta}), + fromResponse: (resp) => resp as {data: NodePeer[]; meta: PeersMeta}, + }, + }, }, getPeer: { - writeReq: (peer_id) => ({params: {peer_id}}), - parseReq: ({params}) => [params.peer_id], - schema: {params: {peer_id: Schema.StringRequired}}, + url: "/eth/v1/node/peers/{peer_id}", + method: "GET", + req: { + writeReq: ({peerId}) => ({params: {peer_id: peerId}}), + parseReq: ({params}) => ({peerId: params.peer_id}), + schema: {params: {peer_id: Schema.StringRequired}}, + }, + resp: JsonOnlyResponseCodec, }, - - getPeerCount: reqEmpty, - getNodeVersion: reqEmpty, - getSyncingStatus: reqEmpty, - getHealth: { - writeReq: (options) => ({ - query: options?.syncingStatus !== undefined ? {syncing_status: options.syncingStatus} : {}, - }), - parseReq: ({query}) => [{syncingStatus: query.syncing_status}], - schema: {query: {syncing_status: Schema.Uint}}, + getPeerCount: { + url: "/eth/v1/node/peer_count", + method: "GET", + req: EmptyRequestCodec, + resp: { + data: PeerCountType, + meta: EmptyMetaCodec, + }, }, - }; -} - -export function getReturnTypes(): ReturnTypes { - const stringType = new StringType(); - const NetworkIdentity = new ContainerType( - { - peerId: stringType, - enr: stringType, - p2pAddresses: ArrayOf(stringType), - discoveryAddresses: ArrayOf(stringType), - metadata: ssz.altair.Metadata, + getNodeVersion: { + url: "/eth/v1/node/version", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, }, - {jsonCase: "eth2"} - ); - - const PeerCount = new ContainerType( - { - disconnected: ssz.UintNum64, - connecting: ssz.UintNum64, - connected: ssz.UintNum64, - disconnecting: ssz.UintNum64, + getSyncingStatus: { + url: "/eth/v1/node/syncing", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + }, + getHealth: { + url: "/eth/v1/node/health", + method: "GET", + req: { + writeReq: ({syncingStatus}) => ({query: {syncing_status: syncingStatus}}), + parseReq: ({query}) => ({syncingStatus: query.syncing_status}), + schema: {query: {syncing_status: Schema.Uint}}, + }, + resp: EmptyResponseCodec, }, - {jsonCase: "eth2"} - ); - - return { - // - // TODO: Consider just converting the JSON case without custom types - // - getNetworkIdentity: ContainerData(NetworkIdentity), - // All these types don't contain any BigInt nor Buffer instances. - // Use jsonType() to translate the casing in a generic way. - getPeers: jsonType("snake"), - getPeer: jsonType("snake"), - getPeerCount: ContainerData(PeerCount), - getNodeVersion: jsonType("snake"), - getSyncingStatus: jsonType("snake"), }; } diff --git a/packages/api/src/beacon/routes/proof.ts b/packages/api/src/beacon/routes/proof.ts index 14964ad63730..5c20a0194fc5 100644 --- a/packages/api/src/beacon/routes/proof.ts +++ b/packages/api/src/beacon/routes/proof.ts @@ -1,63 +1,81 @@ -import {Proof} from "@chainsafe/persistent-merkle-tree"; -import {fromHexString, toHexString} from "@chainsafe/ssz"; -import {ReturnTypes, RoutesData, Schema, sameType, ReqSerializers} from "../../utils/index.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; -import {ApiClientResponse} from "../../interfaces.js"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {CompactMultiProof, ProofType} from "@chainsafe/persistent-merkle-tree"; +import {ByteListType, ContainerType, fromHexString, toHexString} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; +import {ssz} from "@lodestar/types"; +import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; +import {ArrayOf} from "../../utils/codecs.js"; +import {VersionCodec, VersionMeta} from "../../utils/metadata.js"; + +export const CompactMultiProofType = new ContainerType({ + leaves: ArrayOf(ssz.Root, 10000), + descriptor: new ByteListType(2048), +}); // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -export type Api = { +export type Endpoints = { /** * Returns a multiproof of `descriptor` at the requested `stateId`. * The requested `stateId` may not be available. Regular nodes only keep recent states in memory. */ - getStateProof( - stateId: string, - descriptor: Uint8Array - ): Promise>; + getStateProof: Endpoint< + "GET", + {stateId: string; descriptor: Uint8Array}, + {params: {state_id: string}; query: {format: string}}, + CompactMultiProof, + VersionMeta + >; /** * Returns a multiproof of `descriptor` at the requested `blockId`. * The requested `blockId` may not be available. Regular nodes only keep recent states in memory. */ - getBlockProof( - blockId: string, - descriptor: Uint8Array - ): Promise>; -}; - -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - getStateProof: {url: "/eth/v0/beacon/proof/state/{state_id}", method: "GET"}, - getBlockProof: {url: "/eth/v0/beacon/proof/block/{block_id}", method: "GET"}, + getBlockProof: Endpoint< + "GET", + {blockId: string; descriptor: Uint8Array}, + {params: {block_id: string}; query: {format: string}}, + CompactMultiProof, + VersionMeta + >; }; -/* eslint-disable @typescript-eslint/naming-convention */ -export type ReqTypes = { - getStateProof: {params: {state_id: string}; query: {format: string}}; - getBlockProof: {params: {block_id: string}; query: {format: string}}; -}; - -export function getReqSerializers(): ReqSerializers { +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { getStateProof: { - writeReq: (state_id, descriptor) => ({params: {state_id}, query: {format: toHexString(descriptor)}}), - parseReq: ({params, query}) => [params.state_id, fromHexString(query.format)], - schema: {params: {state_id: Schema.StringRequired}, query: {format: Schema.StringRequired}}, + url: "/eth/v0/beacon/proof/state/{state_id}", + method: "GET", + req: { + writeReq: ({stateId, descriptor}) => ({params: {state_id: stateId}, query: {format: toHexString(descriptor)}}), + parseReq: ({params, query}) => ({stateId: params.state_id, descriptor: fromHexString(query.format)}), + schema: {params: {state_id: Schema.StringRequired}, query: {format: Schema.StringRequired}}, + }, + resp: { + data: { + toJson: (data) => CompactMultiProofType.toJson(data), + fromJson: (data) => ({...CompactMultiProofType.fromJson(data), type: ProofType.compactMulti}), + serialize: (data) => CompactMultiProofType.serialize(data), + deserialize: (data) => ({...CompactMultiProofType.deserialize(data), type: ProofType.compactMulti}), + }, + meta: VersionCodec, + }, }, getBlockProof: { - writeReq: (block_id, descriptor) => ({params: {block_id}, query: {format: toHexString(descriptor)}}), - parseReq: ({params, query}) => [params.block_id, fromHexString(query.format)], - schema: {params: {block_id: Schema.StringRequired}, query: {format: Schema.StringRequired}}, + url: "/eth/v0/beacon/proof/block/{block_id}", + method: "GET", + req: { + writeReq: ({blockId, descriptor}) => ({params: {block_id: blockId}, query: {format: toHexString(descriptor)}}), + parseReq: ({params, query}) => ({blockId: params.block_id, descriptor: fromHexString(query.format)}), + schema: {params: {block_id: Schema.StringRequired}, query: {format: Schema.StringRequired}}, + }, + resp: { + data: { + toJson: (data) => CompactMultiProofType.toJson(data), + fromJson: (data) => ({...CompactMultiProofType.fromJson(data), type: ProofType.compactMulti}), + serialize: (data) => CompactMultiProofType.serialize(data), + deserialize: (data) => ({...CompactMultiProofType.deserialize(data), type: ProofType.compactMulti}), + }, + meta: VersionCodec, + }, }, }; } - -export function getReturnTypes(): ReturnTypes { - return { - // Just sent the proof JSON as-is - getStateProof: sameType(), - getBlockProof: sameType(), - }; -} diff --git a/packages/api/src/beacon/routes/validator.ts b/packages/api/src/beacon/routes/validator.ts index 1d6b8b80551c..7f704edd542a 100644 --- a/packages/api/src/beacon/routes/validator.ts +++ b/packages/api/src/beacon/routes/validator.ts @@ -1,45 +1,47 @@ -import {ContainerType, fromHexString, toHexString, Type} from "@chainsafe/ssz"; -import {ForkName, ForkBlobs, isForkBlobs, isForkExecution, ForkPreBlobs, ForkExecution} from "@lodestar/params"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {ContainerType, fromHexString, toHexString, Type, ValueOf} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; +import {isForkBlobs} from "@lodestar/params"; import { allForks, altair, - BLSPubkey, BLSSignature, CommitteeIndex, Epoch, phase0, - bellatrix, Root, Slot, ssz, - UintNum64, UintBn64, ValidatorIndex, - RootHex, - StringType, - SubcommitteeIndex, - Wei, ProducedBlockSource, + stringType, } from "@lodestar/types"; -import {ApiClientResponse} from "../../interfaces.js"; -import {HttpStatusCode} from "../../utils/client/httpStatusCode.js"; +import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; +import {fromGraffitiHex, toBoolean, toGraffitiHex} from "../../utils/serdes.js"; +import {getBlindedForkTypes, toForkName} from "../../utils/fork.js"; import { - RoutesData, - ReturnTypes, ArrayOf, - Schema, + EmptyMeta, + EmptyMetaCodec, + EmptyResponseCodec, + EmptyResponseData, + JsonOnlyReq, + WithMeta, WithVersion, - WithBlockValues, - reqOnlyBody, - ReqSerializers, - jsonType, - ContainerDataExecutionOptimistic, - ContainerData, - TypeJson, -} from "../../utils/index.js"; -import {fromU64Str, fromGraffitiHex, toU64Str, U64Str, toGraffitiHex} from "../../utils/serdes.js"; -import {allForksBlockContentsResSerializer} from "../../utils/routes.js"; -import {ExecutionOptimistic} from "./beacon/block.js"; +} from "../../utils/codecs.js"; +import { + ExecutionOptimisticAndDependentRootCodec, + ExecutionOptimisticAndDependentRootMeta, + ExecutionOptimisticCodec, + ExecutionOptimisticMeta, + MetaHeader, + VersionCodec, + VersionMeta, + VersionType, +} from "../../utils/metadata.js"; + +// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes export enum BuilderSelection { Default = "default", @@ -52,121 +54,193 @@ export enum BuilderSelection { ExecutionOnly = "executiononly", } -export type ExtraProduceBlockOps = { +/** Lodestar-specific (non-standardized) options */ +export type ExtraProduceBlockOpts = { feeRecipient?: string; builderSelection?: BuilderSelection; - builderBoostFactor?: UintBn64; strictFeeRecipientCheck?: boolean; blindedLocal?: boolean; }; -export type ProduceBlockOrContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & ( - | {data: allForks.BeaconBlock; version: ForkPreBlobs} - | {data: allForks.BlockContents; version: ForkBlobs} +export const ProduceBlockV3MetaType = new ContainerType( + { + ...VersionType.fields, + /** Specifies whether the response contains full or blinded block */ + executionPayloadBlinded: ssz.Boolean, + /** Execution payload value in Wei */ + executionPayloadValue: ssz.UintBn64, + /** Consensus rewards paid to the proposer for this block, in Wei */ + consensusBlockValue: ssz.UintBn64, + }, + {jsonCase: "eth2"} ); -export type ProduceBlindedBlockRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & { - data: allForks.BlindedBeaconBlock; - version: ForkExecution; + +export type ProduceBlockV3Meta = ValueOf & { + /** Lodestar-specific (non-standardized) value */ + executionPayloadSource: ProducedBlockSource; }; -export type ProduceFullOrBlindedBlockOrContentsRes = {executionPayloadSource: ProducedBlockSource} & ( - | (ProduceBlockOrContentsRes & {executionPayloadBlinded: false}) - | (ProduceBlindedBlockRes & {executionPayloadBlinded: true}) +export const BlockContentsType = new ContainerType( + { + block: ssz.deneb.BeaconBlock, + kzgProofs: ssz.deneb.KZGProofs, + blobs: ssz.deneb.Blobs, + }, + {jsonCase: "eth2"} ); -// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes - -export type BeaconCommitteeSubscription = { - validatorIndex: ValidatorIndex; - committeeIndex: number; - committeesAtSlot: number; - slot: Slot; - isAggregator: boolean; -}; +export const AttesterDutyType = new ContainerType( + { + /** The validator's public key, uniquely identifying them */ + pubkey: ssz.BLSPubkey, + /** Index of validator in validator registry */ + validatorIndex: ssz.ValidatorIndex, + /** Index of the committee */ + committeeIndex: ssz.CommitteeIndex, + /** Number of validators in committee */ + committeeLength: ssz.UintNum64, + /** Number of committees at the provided slot */ + committeesAtSlot: ssz.UintNum64, + /** Index of validator in committee */ + validatorCommitteeIndex: ssz.UintNum64, + /** The slot at which the validator must attest */ + slot: ssz.Slot, + }, + {jsonCase: "eth2"} +); -/** - * From https://github.com/ethereum/beacon-APIs/pull/136 - */ -export type SyncCommitteeSubscription = { - validatorIndex: ValidatorIndex; - syncCommitteeIndices: number[]; - untilEpoch: Epoch; -}; +export const ProposerDutyType = new ContainerType( + { + slot: ssz.Slot, + validatorIndex: ssz.ValidatorIndex, + pubkey: ssz.BLSPubkey, + }, + {jsonCase: "eth2"} +); /** - * The types used here are string instead of ssz based because the use of proposer data - * is just validator --> beacon json api call for `beaconProposerCache` cache update. + * From https://github.com/ethereum/beacon-APIs/pull/134 */ -export type ProposerPreparationData = { - validatorIndex: string; - feeRecipient: string; -}; - -export type ProposerDuty = { - slot: Slot; - validatorIndex: ValidatorIndex; - pubkey: BLSPubkey; -}; +export const SyncDutyType = new ContainerType( + { + pubkey: ssz.BLSPubkey, + /** Index of validator in validator registry. */ + validatorIndex: ssz.ValidatorIndex, + /** The indices of the validator in the sync committee. */ + validatorSyncCommitteeIndices: ArrayOf(ssz.CommitteeIndex), + }, + {jsonCase: "eth2"} +); -export type AttesterDuty = { - // The validator's public key, uniquely identifying them - pubkey: BLSPubkey; - // Index of validator in validator registry - validatorIndex: ValidatorIndex; - committeeIndex: CommitteeIndex; - // Number of validators in committee - committeeLength: UintNum64; - // Number of committees at the provided slot - committeesAtSlot: UintNum64; - // Index of validator in committee - validatorCommitteeIndex: UintNum64; - // The slot at which the validator must attest. - slot: Slot; -}; +export const BeaconCommitteeSubscriptionType = new ContainerType( + { + validatorIndex: ssz.ValidatorIndex, + committeeIndex: ssz.CommitteeIndex, + committeesAtSlot: ssz.Slot, + slot: ssz.Slot, + isAggregator: ssz.Boolean, + }, + {jsonCase: "eth2"} +); /** - * From https://github.com/ethereum/beacon-APIs/pull/134 + * From https://github.com/ethereum/beacon-APIs/pull/136 */ -export type SyncDuty = { - pubkey: BLSPubkey; - /** Index of validator in validator registry. */ - validatorIndex: ValidatorIndex; - /** The indices of the validator in the sync committee. */ - validatorSyncCommitteeIndices: number[]; -}; +export const SyncCommitteeSubscriptionType = new ContainerType( + { + validatorIndex: ssz.ValidatorIndex, + syncCommitteeIndices: ArrayOf(ssz.CommitteeIndex), + untilEpoch: ssz.Epoch, + }, + {jsonCase: "eth2"} +); + +export const ProposerPreparationDataType = new ContainerType( + { + validatorIndex: ssz.ValidatorIndex, + feeRecipient: stringType, + }, + {jsonCase: "eth2"} +); /** * From https://github.com/ethereum/beacon-APIs/pull/224 */ -export type BeaconCommitteeSelection = { - /** Index of the validator */ - validatorIndex: ValidatorIndex; - /** The slot at which a validator is assigned to attest */ - slot: Slot; - /** The `slot_signature` calculated by the validator for the upcoming attestation slot */ - selectionProof: BLSSignature; -}; +export const BeaconCommitteeSelectionType = new ContainerType( + { + /** Index of the validator */ + validatorIndex: ssz.ValidatorIndex, + /** The slot at which a validator is assigned to attest */ + slot: ssz.Slot, + /** The `slot_signature` calculated by the validator for the upcoming attestation slot */ + selectionProof: ssz.BLSSignature, + }, + {jsonCase: "eth2"} +); /** * From https://github.com/ethereum/beacon-APIs/pull/224 */ -export type SyncCommitteeSelection = { - /** Index of the validator */ - validatorIndex: ValidatorIndex; - /** The slot at which validator is assigned to produce a sync committee contribution */ - slot: Slot; - /** SubcommitteeIndex to which the validator is assigned */ - subcommitteeIndex: SubcommitteeIndex; - /** The `slot_signature` calculated by the validator for the upcoming sync committee slot */ - selectionProof: BLSSignature; -}; +export const SyncCommitteeSelectionType = new ContainerType( + { + /** Index of the validator */ + validatorIndex: ssz.ValidatorIndex, + /** The slot at which validator is assigned to produce a sync committee contribution */ + slot: ssz.Slot, + /** SubcommitteeIndex to which the validator is assigned */ + subcommitteeIndex: ssz.SubcommitteeIndex, + /** The `slot_signature` calculated by the validator for the upcoming sync committee slot */ + selectionProof: ssz.BLSSignature, + }, + {jsonCase: "eth2"} +); -export type LivenessResponseData = { - index: ValidatorIndex; - isLive: boolean; -}; +export const LivenessResponseDataType = new ContainerType( + { + index: ssz.ValidatorIndex, + isLive: ssz.Boolean, + }, + {jsonCase: "eth2"} +); -export type Api = { +export const ValidatorIndicesType = ArrayOf(ssz.ValidatorIndex); +export const AttesterDutyListType = ArrayOf(AttesterDutyType); +export const ProposerDutyListType = ArrayOf(ProposerDutyType); +export const SyncDutyListType = ArrayOf(SyncDutyType); +export const SignedAggregateAndProofListType = ArrayOf(ssz.phase0.SignedAggregateAndProof); +export const SignedContributionAndProofListType = ArrayOf(ssz.altair.SignedContributionAndProof); +export const BeaconCommitteeSubscriptionListType = ArrayOf(BeaconCommitteeSubscriptionType); +export const SyncCommitteeSubscriptionListType = ArrayOf(SyncCommitteeSubscriptionType); +export const ProposerPreparationDataListType = ArrayOf(ProposerPreparationDataType); +export const BeaconCommitteeSelectionListType = ArrayOf(BeaconCommitteeSelectionType); +export const SyncCommitteeSelectionListType = ArrayOf(SyncCommitteeSelectionType); +export const LivenessResponseDataListType = ArrayOf(LivenessResponseDataType); +export const SignedValidatorRegistrationV1ListType = ArrayOf(ssz.bellatrix.SignedValidatorRegistrationV1); + +export type ValidatorIndices = ValueOf; +export type AttesterDuty = ValueOf; +export type AttesterDutyList = ValueOf; +export type ProposerDuty = ValueOf; +export type ProposerDutyList = ValueOf; +export type SyncDuty = ValueOf; +export type SyncDutyList = ValueOf; +export type SignedAggregateAndProofList = ValueOf; +export type SignedContributionAndProofList = ValueOf; +export type BeaconCommitteeSubscription = ValueOf; +export type BeaconCommitteeSubscriptionList = ValueOf; +export type SyncCommitteeSubscription = ValueOf; +export type SyncCommitteeSubscriptionList = ValueOf; +export type ProposerPreparationData = ValueOf; +export type ProposerPreparationDataList = ValueOf; +export type BeaconCommitteeSelection = ValueOf; +export type BeaconCommitteeSelectionList = ValueOf; +export type SyncCommitteeSelection = ValueOf; +export type SyncCommitteeSelectionList = ValueOf; +export type LivenessResponseData = ValueOf; +export type LivenessResponseDataList = ValueOf; +export type SignedValidatorRegistrationV1List = ValueOf; + +export type Endpoints = { /** * Get attester duties * Requests the beacon node to provide a set of attestation duties, which should be performed by validators, for a particular epoch. @@ -175,19 +249,18 @@ export type Api = { * - event.current_duty_dependent_root when `compute_epoch_at_slot(event.slot) + 1 == epoch` * - event.block otherwise * The dependent_root value is `get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1)` or the genesis block root in the case of underflow. - * @param epoch Should only be allowed 1 epoch ahead - * @param requestBody An array of the validator indices for which to obtain the duties. - * @returns any Success response - * @throws ApiError */ - getAttesterDuties( - epoch: Epoch, - validatorIndices: ValidatorIndex[] - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: AttesterDuty[]; executionOptimistic: ExecutionOptimistic; dependentRoot: RootHex}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + getAttesterDuties: Endpoint< + "POST", + { + /** Should only be allowed 1 epoch ahead */ + epoch: Epoch; + /** An array of the validator indices for which to obtain the duties */ + indices: ValidatorIndices; + }, + {params: {epoch: Epoch}; body: unknown}, + AttesterDutyList, + ExecutionOptimisticAndDependentRootMeta >; /** @@ -197,169 +270,185 @@ export type Api = { * - event.current_duty_dependent_root when `compute_epoch_at_slot(event.slot) == epoch` * - event.block otherwise * The dependent_root value is `get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch) - 1)` or the genesis block root in the case of underflow. - * @param epoch - * @returns any Success response - * @throws ApiError */ - getProposerDuties( - epoch: Epoch - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: ProposerDuty[]; executionOptimistic: ExecutionOptimistic; dependentRoot: RootHex}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + getProposerDuties: Endpoint< + "GET", + {epoch: Epoch}, + {params: {epoch: Epoch}}, + ProposerDutyList, + ExecutionOptimisticAndDependentRootMeta >; - getSyncCommitteeDuties( - epoch: number, - validatorIndices: ValidatorIndex[] - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: SyncDuty[]; executionOptimistic: ExecutionOptimistic}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + getSyncCommitteeDuties: Endpoint< + "POST", + { + epoch: number; + indices: ValidatorIndices; + }, + {params: {epoch: Epoch}; body: unknown}, + SyncDutyList, + ExecutionOptimisticMeta >; /** * Produce a new block, without signature. * Requests a beacon node to produce a valid block, which can then be signed by a validator. - * @param slot The slot for which the block should be proposed. - * @param randaoReveal The validator's randao reveal value. - * @param graffiti Arbitrary data validator wants to include in block. - * @returns any Success response - * @throws ApiError */ - produceBlock( - slot: Slot, - randaoReveal: BLSSignature, - graffiti: string - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: allForks.BeaconBlock}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + produceBlock: Endpoint< + "GET", + { + /** The slot for which the block should be proposed. */ + slot: Slot; + /** The validator's randao reveal value */ + randaoReveal: BLSSignature; + /** Arbitrary data validator wants to include in block */ + graffiti: string; + }, + {params: {slot: number}; query: {randao_reveal: string; graffiti: string}}, + allForks.BeaconBlock, + VersionMeta >; /** * Requests a beacon node to produce a valid block, which can then be signed by a validator. * Metadata in the response indicates the type of block produced, and the supported types of block * will be added to as forks progress. - * @param slot The slot for which the block should be proposed. - * @param randaoReveal The validator's randao reveal value. - * @param graffiti Arbitrary data validator wants to include in block. - * @returns any Success response - * @throws ApiError */ - produceBlockV2( - slot: Slot, - randaoReveal: BLSSignature, - graffiti: string - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: ProduceBlockOrContentsRes}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + produceBlockV2: Endpoint< + "GET", + { + /** The slot for which the block should be proposed */ + slot: Slot; + /** The validator's randao reveal value */ + randaoReveal: BLSSignature; + /** Arbitrary data validator wants to include in block */ + graffiti: string; + } & Omit, + { + params: {slot: number}; + query: { + randao_reveal: string; + graffiti: string; + fee_recipient?: string; + builder_selection?: string; + strict_fee_recipient_check?: boolean; + }; + }, + allForks.BeaconBlockOrContents, + VersionMeta >; /** * Requests a beacon node to produce a valid block, which can then be signed by a validator. * Metadata in the response indicates the type of block produced, and the supported types of block * will be added to as forks progress. - * @param slot The slot for which the block should be proposed. - * @param randaoReveal The validator's randao reveal value. - * @param graffiti Arbitrary data validator wants to include in block. - * @returns any Success response - * @throws ApiError */ - produceBlockV3( - slot: Slot, - randaoReveal: BLSSignature, - graffiti: string, - skipRandaoVerification?: boolean, - opts?: ExtraProduceBlockOps - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: ProduceFullOrBlindedBlockOrContentsRes; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + produceBlockV3: Endpoint< + "GET", + { + /** The slot for which the block should be proposed */ + slot: Slot; + /** The validator's randao reveal value */ + randaoReveal: BLSSignature; + /** Arbitrary data validator wants to include in block */ + graffiti: string; + skipRandaoVerification?: boolean; + builderBoostFactor?: UintBn64; + } & ExtraProduceBlockOpts, + { + params: {slot: number}; + query: { + randao_reveal: string; + graffiti: string; + skip_randao_verification?: string; + fee_recipient?: string; + builder_selection?: string; + builder_boost_factor?: string; + strict_fee_recipient_check?: boolean; + blinded_local?: boolean; + }; + }, + allForks.FullOrBlindedBeaconBlockOrContents, + ProduceBlockV3Meta >; - produceBlindedBlock( - slot: Slot, - randaoReveal: BLSSignature, - graffiti: string - ): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: ProduceBlindedBlockRes; - }, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + produceBlindedBlock: Endpoint< + "GET", + { + slot: Slot; + randaoReveal: BLSSignature; + graffiti: string; + }, + {params: {slot: number}; query: {randao_reveal: string; graffiti: string}}, + allForks.BlindedBeaconBlock, + VersionMeta >; /** * Produce an attestation data * Requests that the beacon node produce an AttestationData. - * @param slot The slot for which an attestation data should be created. - * @param committeeIndex The committee index for which an attestation data should be created. - * @returns any Success response - * @throws ApiError */ - produceAttestationData( - index: CommitteeIndex, - slot: Slot - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: phase0.AttestationData}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + produceAttestationData: Endpoint< + "GET", + { + /** The committee index for which an attestation data should be created */ + committeeIndex: CommitteeIndex; + /** The slot for which an attestation data should be created */ + slot: Slot; + }, + {query: {slot: number; committee_index: number}}, + phase0.AttestationData, + EmptyMeta >; - produceSyncCommitteeContribution( - slot: Slot, - subcommitteeIndex: number, - beaconBlockRoot: Root - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: altair.SyncCommitteeContribution}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE - > + produceSyncCommitteeContribution: Endpoint< + "GET", + { + slot: Slot; + subcommitteeIndex: number; + beaconBlockRoot: Root; + }, + {query: {slot: number; subcommittee_index: number; beacon_block_root: string}}, + altair.SyncCommitteeContribution, + EmptyMeta >; /** * Get aggregated attestation * Aggregates all attestations matching given attestation data root and slot - * @param attestationDataRoot HashTreeRoot of AttestationData that validator want's aggregated - * @param slot - * @returns any Returns aggregated `Attestation` object with same `AttestationData` root. - * @throws ApiError + * Returns an aggregated `Attestation` object with same `AttestationData` root. */ - getAggregatedAttestation( - attestationDataRoot: Root, - slot: Slot - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: phase0.Attestation}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND - > + getAggregatedAttestation: Endpoint< + "GET", + { + /** HashTreeRoot of AttestationData that validator want's aggregated */ + attestationDataRoot: Root; + slot: Slot; + }, + {query: {attestation_data_root: string; slot: number}}, + phase0.Attestation, + EmptyMeta >; /** * Publish multiple aggregate and proofs * Verifies given aggregate and proofs and publishes them on appropriate gossipsub topic. - * @param requestBody - * @returns any Successful response - * @throws ApiError */ - publishAggregateAndProofs( - signedAggregateAndProofs: phase0.SignedAggregateAndProof[] - ): Promise>; + publishAggregateAndProofs: Endpoint< + "POST", + {signedAggregateAndProofs: SignedAggregateAndProofList}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; - publishContributionAndProofs( - contributionAndProofs: altair.SignedContributionAndProof[] - ): Promise>; + publishContributionAndProofs: Endpoint< + "POST", + {contributionAndProofs: SignedContributionAndProofList}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; /** * Signal beacon node to prepare for a committee subnet @@ -370,28 +459,33 @@ export type Api = { * - announce subnet topic subscription on gossipsub * - aggregate attestations received on that subnet * - * @param requestBody - * @returns any Slot signature is valid and beacon node has prepared the attestation subnet. + * Returns if slot signature is valid and beacon node has prepared the attestation subnet. * - * Note that, we cannot be certain Beacon node will find peers for that subnet for various reasons," - * - * @throws ApiError + * Note that we cannot be certain the Beacon node will find peers for that subnet for various reasons. */ - prepareBeaconCommitteeSubnet( - subscriptions: BeaconCommitteeSubscription[] - ): Promise< - ApiClientResponse<{[HttpStatusCode.OK]: void}, HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE> + prepareBeaconCommitteeSubnet: Endpoint< + "POST", + {subscriptions: BeaconCommitteeSubscriptionList}, + {body: unknown}, + EmptyResponseData, + EmptyMeta >; - prepareSyncCommitteeSubnets( - subscriptions: SyncCommitteeSubscription[] - ): Promise< - ApiClientResponse<{[HttpStatusCode.OK]: void}, HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE> + prepareSyncCommitteeSubnets: Endpoint< + "POST", + {subscriptions: SyncCommitteeSubscriptionList}, + {body: unknown}, + EmptyResponseData, + EmptyMeta >; - prepareBeaconProposer( - proposers: ProposerPreparationData[] - ): Promise>; + prepareBeaconProposer: Endpoint< + "POST", + {proposers: ProposerPreparationDataList}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; /** * Determine if a distributed validator has been selected to aggregate attestations @@ -403,17 +497,17 @@ export type Api = { * * Note that this endpoint is not implemented by the beacon node and will return a 501 error * - * @param requestBody An array of partial beacon committee selection proofs - * @returns An array of threshold aggregated beacon committee selection proofs - * @throws ApiError + * Returns an array of threshold aggregated beacon committee selection proofs */ - submitBeaconCommitteeSelections( - selections: BeaconCommitteeSelection[] - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: BeaconCommitteeSelection[]}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_IMPLEMENTED | HttpStatusCode.SERVICE_UNAVAILABLE - > + submitBeaconCommitteeSelections: Endpoint< + "POST", + { + /** An array of partial beacon committee selection proofs */ + selections: BeaconCommitteeSelectionList; + }, + {body: unknown}, + BeaconCommitteeSelectionList, + EmptyMeta >; /** @@ -426,370 +520,481 @@ export type Api = { * * Note that this endpoint is not implemented by the beacon node and will return a 501 error * - * @param requestBody An array of partial sync committee selection proofs - * @returns An array of threshold aggregated sync committee selection proofs - * @throws ApiError + * Returns an array of threshold aggregated sync committee selection proofs */ - submitSyncCommitteeSelections( - selections: SyncCommitteeSelection[] - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: SyncCommitteeSelection[]}}, - HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_IMPLEMENTED | HttpStatusCode.SERVICE_UNAVAILABLE - > + submitSyncCommitteeSelections: Endpoint< + "POST", + { + /** An array of partial sync committee selection proofs */ + selections: SyncCommitteeSelectionList; + }, + {body: unknown}, + SyncCommitteeSelectionList, + EmptyMeta >; /** Returns validator indices that have been observed to be active on the network */ - getLiveness( - epoch: Epoch, - validatorIndices: ValidatorIndex[] - ): Promise>; - - registerValidator( - registrations: bellatrix.SignedValidatorRegistrationV1[] - ): Promise>; -}; - -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - getAttesterDuties: {url: "/eth/v1/validator/duties/attester/{epoch}", method: "POST"}, - getProposerDuties: {url: "/eth/v1/validator/duties/proposer/{epoch}", method: "GET"}, - getSyncCommitteeDuties: {url: "/eth/v1/validator/duties/sync/{epoch}", method: "POST"}, - produceBlock: {url: "/eth/v1/validator/blocks/{slot}", method: "GET"}, - produceBlockV2: {url: "/eth/v2/validator/blocks/{slot}", method: "GET"}, - produceBlockV3: {url: "/eth/v3/validator/blocks/{slot}", method: "GET"}, - produceBlindedBlock: {url: "/eth/v1/validator/blinded_blocks/{slot}", method: "GET"}, - produceAttestationData: {url: "/eth/v1/validator/attestation_data", method: "GET"}, - produceSyncCommitteeContribution: {url: "/eth/v1/validator/sync_committee_contribution", method: "GET"}, - getAggregatedAttestation: {url: "/eth/v1/validator/aggregate_attestation", method: "GET"}, - publishAggregateAndProofs: {url: "/eth/v1/validator/aggregate_and_proofs", method: "POST"}, - publishContributionAndProofs: {url: "/eth/v1/validator/contribution_and_proofs", method: "POST"}, - prepareBeaconCommitteeSubnet: {url: "/eth/v1/validator/beacon_committee_subscriptions", method: "POST"}, - prepareSyncCommitteeSubnets: {url: "/eth/v1/validator/sync_committee_subscriptions", method: "POST"}, - prepareBeaconProposer: {url: "/eth/v1/validator/prepare_beacon_proposer", method: "POST"}, - submitBeaconCommitteeSelections: {url: "/eth/v1/validator/beacon_committee_selections", method: "POST"}, - submitSyncCommitteeSelections: {url: "/eth/v1/validator/sync_committee_selections", method: "POST"}, - getLiveness: {url: "/eth/v1/validator/liveness/{epoch}", method: "POST"}, - registerValidator: {url: "/eth/v1/validator/register_validator", method: "POST"}, -}; + getLiveness: Endpoint< + "POST", + { + epoch: Epoch; + indices: ValidatorIndex[]; + }, + {params: {epoch: Epoch}; body: unknown}, + LivenessResponseDataList, + EmptyMeta + >; -/* eslint-disable @typescript-eslint/naming-convention */ -export type ReqTypes = { - getAttesterDuties: {params: {epoch: Epoch}; body: U64Str[]}; - getProposerDuties: {params: {epoch: Epoch}}; - getSyncCommitteeDuties: {params: {epoch: Epoch}; body: U64Str[]}; - produceBlock: {params: {slot: number}; query: {randao_reveal: string; graffiti: string}}; - produceBlockV2: {params: {slot: number}; query: {randao_reveal: string; graffiti: string; fee_recipient?: string}}; - produceBlockV3: { - params: {slot: number}; - query: { - randao_reveal: string; - graffiti: string; - skip_randao_verification?: string; - fee_recipient?: string; - builder_selection?: string; - builder_boost_factor?: string; - strict_fee_recipient_check?: boolean; - blinded_local?: boolean; - }; - }; - produceBlindedBlock: {params: {slot: number}; query: {randao_reveal: string; graffiti: string}}; - produceAttestationData: {query: {slot: number; committee_index: number}}; - produceSyncCommitteeContribution: {query: {slot: number; subcommittee_index: number; beacon_block_root: string}}; - getAggregatedAttestation: {query: {attestation_data_root: string; slot: number}}; - publishAggregateAndProofs: {body: unknown}; - publishContributionAndProofs: {body: unknown}; - prepareBeaconCommitteeSubnet: {body: unknown}; - prepareSyncCommitteeSubnets: {body: unknown}; - prepareBeaconProposer: {body: unknown}; - submitBeaconCommitteeSelections: {body: unknown}; - submitSyncCommitteeSelections: {body: unknown}; - getLiveness: {params: {epoch: Epoch}; body: U64Str[]}; - registerValidator: {body: unknown}; + registerValidator: Endpoint< + "POST", + {registrations: SignedValidatorRegistrationV1List}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; }; -const BeaconCommitteeSelection = new ContainerType( - { - validatorIndex: ssz.ValidatorIndex, - slot: ssz.Slot, - selectionProof: ssz.BLSSignature, - }, - {jsonCase: "eth2"} -); - -const SyncCommitteeSelection = new ContainerType( - { - validatorIndex: ssz.ValidatorIndex, - slot: ssz.Slot, - subcommitteeIndex: ssz.SubcommitteeIndex, - selectionProof: ssz.BLSSignature, - }, - {jsonCase: "eth2"} -); - -export function getReqSerializers(): ReqSerializers { - const BeaconCommitteeSubscription = new ContainerType( - { - validatorIndex: ssz.ValidatorIndex, - committeeIndex: ssz.CommitteeIndex, - committeesAtSlot: ssz.Slot, - slot: ssz.Slot, - isAggregator: ssz.Boolean, +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { + return { + getAttesterDuties: { + url: "/eth/v1/validator/duties/attester/{epoch}", + method: "POST", + req: { + writeReqJson: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.toJson(indices)}), + parseReqJson: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.fromJson(body)}), + writeReqSsz: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.serialize(indices)}), + parseReqSsz: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.deserialize(body)}), + schema: { + params: {epoch: Schema.UintRequired}, + body: Schema.StringArray, + }, + }, + resp: { + data: AttesterDutyListType, + meta: ExecutionOptimisticAndDependentRootCodec, + }, }, - {jsonCase: "eth2"} - ); - - const SyncCommitteeSubscription = new ContainerType( - { - validatorIndex: ssz.ValidatorIndex, - syncCommitteeIndices: ArrayOf(ssz.CommitteeIndex), - untilEpoch: ssz.Epoch, + getProposerDuties: { + url: "/eth/v1/validator/duties/proposer/{epoch}", + method: "GET", + req: { + writeReq: ({epoch}) => ({params: {epoch}}), + parseReq: ({params}) => ({epoch: params.epoch}), + schema: { + params: {epoch: Schema.UintRequired}, + }, + }, + resp: { + data: ProposerDutyListType, + meta: ExecutionOptimisticAndDependentRootCodec, + }, }, - {jsonCase: "eth2"} - ); - - const produceBlockV3: ReqSerializers["produceBlockV3"] = { - writeReq: (slot, randaoReveal, graffiti, skipRandaoVerification, opts) => ({ - params: {slot}, - query: { - randao_reveal: toHexString(randaoReveal), - graffiti: toGraffitiHex(graffiti), - fee_recipient: opts?.feeRecipient, - ...(skipRandaoVerification && {skip_randao_verification: ""}), - builder_selection: opts?.builderSelection, - builder_boost_factor: opts?.builderBoostFactor?.toString(), - strict_fee_recipient_check: opts?.strictFeeRecipientCheck, - blinded_local: opts?.blindedLocal, + getSyncCommitteeDuties: { + url: "/eth/v1/validator/duties/sync/{epoch}", + method: "POST", + req: { + writeReqJson: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.toJson(indices)}), + parseReqJson: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.fromJson(body)}), + writeReqSsz: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.serialize(indices)}), + parseReqSsz: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.deserialize(body)}), + schema: { + params: {epoch: Schema.UintRequired}, + body: Schema.StringArray, + }, }, - }), - parseReq: ({params, query}) => [ - params.slot, - fromHexString(query.randao_reveal), - fromGraffitiHex(query.graffiti), - parseSkipRandaoVerification(query.skip_randao_verification), - { - feeRecipient: query.fee_recipient, - builderSelection: query.builder_selection as BuilderSelection, - builderBoostFactor: parseBuilderBoostFactor(query.builder_boost_factor), - strictFeeRecipientCheck: query.strict_fee_recipient_check, - blindedLocal: query.blinded_local, + resp: { + data: SyncDutyListType, + meta: ExecutionOptimisticCodec, }, - ], - schema: { - params: {slot: Schema.UintRequired}, - query: { - randao_reveal: Schema.StringRequired, - graffiti: Schema.String, - fee_recipient: Schema.String, - skip_randao_verification: Schema.String, - builder_selection: Schema.String, - builder_boost_factor: Schema.String, - strict_fee_recipient_check: Schema.Boolean, - blinded_local: Schema.Boolean, + }, + produceBlock: { + url: "/eth/v1/validator/blocks/{slot}", + method: "GET", + req: { + writeReq: ({slot, randaoReveal, graffiti}) => ({ + params: {slot}, + query: {randao_reveal: toHexString(randaoReveal), graffiti: toGraffitiHex(graffiti)}, + }), + parseReq: ({params, query}) => ({ + slot: params.slot, + randaoReveal: fromHexString(query.randao_reveal), + graffiti: fromGraffitiHex(query.graffiti), + }), + schema: { + params: {slot: Schema.UintRequired}, + query: { + randao_reveal: Schema.StringRequired, + graffiti: Schema.String, + }, + }, + }, + resp: { + data: WithVersion((fork) => ssz[fork].BeaconBlock), + meta: VersionCodec, }, }, - }; - - return { - getAttesterDuties: { - writeReq: (epoch, indexes) => ({params: {epoch}, body: indexes.map((i) => toU64Str(i))}), - parseReq: ({params, body}) => [params.epoch, body.map((i) => fromU64Str(i))], - schema: { - params: {epoch: Schema.UintRequired}, - body: Schema.StringArray, + produceBlockV2: { + url: "/eth/v2/validator/blocks/{slot}", + method: "GET", + req: { + writeReq: ({slot, randaoReveal, graffiti, feeRecipient, builderSelection, strictFeeRecipientCheck}) => ({ + params: {slot}, + query: { + randao_reveal: toHexString(randaoReveal), + graffiti: toGraffitiHex(graffiti), + fee_recipient: feeRecipient, + builder_selection: builderSelection, + strict_fee_recipient_check: strictFeeRecipientCheck, + }, + }), + parseReq: ({params, query}) => ({ + slot: params.slot, + randaoReveal: fromHexString(query.randao_reveal), + graffiti: fromGraffitiHex(query.graffiti), + feeRecipient: query.fee_recipient, + builderSelection: query.builder_selection as BuilderSelection, + strictFeeRecipientCheck: query.strict_fee_recipient_check, + }), + schema: { + params: {slot: Schema.UintRequired}, + query: { + randao_reveal: Schema.StringRequired, + graffiti: Schema.String, + fee_recipient: Schema.String, + builder_selection: Schema.String, + strict_fee_recipient_check: Schema.Boolean, + }, + }, + }, + resp: { + data: WithVersion( + (fork) => + (isForkBlobs(fork) ? BlockContentsType : ssz[fork].BeaconBlock) as Type + ), + meta: VersionCodec, }, }, - - getProposerDuties: { - writeReq: (epoch) => ({params: {epoch}}), - parseReq: ({params}) => [params.epoch], - schema: { - params: {epoch: Schema.UintRequired}, + produceBlockV3: { + url: "/eth/v3/validator/blocks/{slot}", + method: "GET", + req: { + writeReq: ({ + slot, + randaoReveal, + graffiti, + skipRandaoVerification, + feeRecipient, + builderSelection, + builderBoostFactor, + strictFeeRecipientCheck, + blindedLocal, + }) => ({ + params: {slot}, + query: { + randao_reveal: toHexString(randaoReveal), + graffiti: toGraffitiHex(graffiti), + skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification), + fee_recipient: feeRecipient, + builder_selection: builderSelection, + builder_boost_factor: builderBoostFactor?.toString(), + strict_fee_recipient_check: strictFeeRecipientCheck, + blinded_local: blindedLocal, + }, + }), + parseReq: ({params, query}) => ({ + slot: params.slot, + randaoReveal: fromHexString(query.randao_reveal), + graffiti: fromGraffitiHex(query.graffiti), + skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification), + feeRecipient: query.fee_recipient, + builderSelection: query.builder_selection as BuilderSelection, + builderBoostFactor: parseBuilderBoostFactor(query.builder_boost_factor), + strictFeeRecipientCheck: query.strict_fee_recipient_check, + blindedLocal: query.blinded_local, + }), + schema: { + params: {slot: Schema.UintRequired}, + query: { + randao_reveal: Schema.StringRequired, + graffiti: Schema.String, + skip_randao_verification: Schema.String, + fee_recipient: Schema.String, + builder_selection: Schema.String, + builder_boost_factor: Schema.String, + strict_fee_recipient_check: Schema.Boolean, + blinded_local: Schema.Boolean, + }, + }, + }, + resp: { + data: WithMeta( + ({version, executionPayloadBlinded}) => + (executionPayloadBlinded + ? getBlindedForkTypes(version).BeaconBlock + : isForkBlobs(version) + ? BlockContentsType + : ssz[version].BeaconBlock) as Type + ), + meta: { + toJson: (meta) => ({ + ...ProduceBlockV3MetaType.toJson(meta), + execution_payload_source: meta.executionPayloadSource, + }), + fromJson: (val) => { + const {executionPayloadBlinded, ...meta} = ProduceBlockV3MetaType.fromJson(val); + + // Extract source from the data and assign defaults in the spec compliant manner if not present + const executionPayloadSource = + (val as {execution_payload_source: ProducedBlockSource}).execution_payload_source ?? + (executionPayloadBlinded === true ? ProducedBlockSource.builder : ProducedBlockSource.engine); + + return {...meta, executionPayloadBlinded, executionPayloadSource}; + }, + toHeadersObject: (meta) => ({ + [MetaHeader.Version]: meta.version, + [MetaHeader.ExecutionPayloadBlinded]: meta.executionPayloadBlinded.toString(), + [MetaHeader.ExecutionPayloadSource]: meta.executionPayloadSource.toString(), + [MetaHeader.ExecutionPayloadValue]: meta.executionPayloadValue.toString(), + [MetaHeader.ConsensusBlockValue]: meta.consensusBlockValue.toString(), + }), + fromHeaders: (headers) => { + const executionPayloadBlinded = toBoolean(headers.getRequired(MetaHeader.ExecutionPayloadBlinded)); + + // Extract source from the headers and assign defaults in a spec compliant manner if not present + const executionPayloadSource = + (headers.get(MetaHeader.ExecutionPayloadSource) as ProducedBlockSource) ?? + (executionPayloadBlinded === true ? ProducedBlockSource.builder : ProducedBlockSource.engine); + + return { + version: toForkName(headers.getRequired(MetaHeader.Version)), + executionPayloadBlinded, + executionPayloadSource, + executionPayloadValue: BigInt(headers.getRequired(MetaHeader.ExecutionPayloadValue)), + consensusBlockValue: BigInt(headers.getRequired(MetaHeader.ConsensusBlockValue)), + }; + }, + }, }, }, - - getSyncCommitteeDuties: { - writeReq: (epoch, indexes) => ({params: {epoch}, body: indexes.map((i) => toU64Str(i))}), - parseReq: ({params, body}) => [params.epoch, body.map((i) => fromU64Str(i))], - schema: { - params: {epoch: Schema.UintRequired}, - body: Schema.StringArray, + produceBlindedBlock: { + url: "/eth/v1/validator/blinded_blocks/{slot}", + method: "GET", + req: { + writeReq: ({slot, randaoReveal, graffiti}) => ({ + params: {slot}, + query: {randao_reveal: toHexString(randaoReveal), graffiti: toGraffitiHex(graffiti)}, + }), + parseReq: ({params, query}) => ({ + slot: params.slot, + randaoReveal: fromHexString(query.randao_reveal), + graffiti: fromGraffitiHex(query.graffiti), + }), + schema: { + params: {slot: Schema.UintRequired}, + query: { + randao_reveal: Schema.StringRequired, + graffiti: Schema.String, + }, + }, + }, + resp: { + data: WithVersion((fork) => getBlindedForkTypes(fork).BeaconBlock), + meta: VersionCodec, }, }, - - produceBlock: produceBlockV3 as ReqSerializers["produceBlock"], - produceBlockV2: produceBlockV3 as ReqSerializers["produceBlockV2"], - produceBlockV3, - produceBlindedBlock: produceBlockV3 as ReqSerializers["produceBlindedBlock"], - produceAttestationData: { - writeReq: (index, slot) => ({query: {slot, committee_index: index}}), - parseReq: ({query}) => [query.committee_index, query.slot], - schema: { - query: {slot: Schema.UintRequired, committee_index: Schema.UintRequired}, + url: "/eth/v1/validator/attestation_data", + method: "GET", + req: { + writeReq: ({committeeIndex, slot}) => ({query: {slot, committee_index: committeeIndex}}), + parseReq: ({query}) => ({committeeIndex: query.committee_index, slot: query.slot}), + schema: { + query: {slot: Schema.UintRequired, committee_index: Schema.UintRequired}, + }, + }, + resp: { + data: ssz.phase0.AttestationData, + meta: EmptyMetaCodec, }, }, - produceSyncCommitteeContribution: { - writeReq: (slot, index, root) => ({ - query: {slot, subcommittee_index: index, beacon_block_root: toHexString(root)}, - }), - parseReq: ({query}) => [query.slot, query.subcommittee_index, fromHexString(query.beacon_block_root)], - schema: { - query: { - slot: Schema.UintRequired, - subcommittee_index: Schema.UintRequired, - beacon_block_root: Schema.StringRequired, + url: "/eth/v1/validator/sync_committee_contribution", + method: "GET", + req: { + writeReq: ({slot, subcommitteeIndex, beaconBlockRoot}) => ({ + query: {slot, subcommittee_index: subcommitteeIndex, beacon_block_root: toHexString(beaconBlockRoot)}, + }), + parseReq: ({query}) => ({ + slot: query.slot, + subcommitteeIndex: query.subcommittee_index, + beaconBlockRoot: fromHexString(query.beacon_block_root), + }), + schema: { + query: { + slot: Schema.UintRequired, + subcommittee_index: Schema.UintRequired, + beacon_block_root: Schema.StringRequired, + }, }, }, + resp: { + data: ssz.altair.SyncCommitteeContribution, + meta: EmptyMetaCodec, + }, }, - getAggregatedAttestation: { - writeReq: (root, slot) => ({query: {attestation_data_root: toHexString(root), slot}}), - parseReq: ({query}) => [fromHexString(query.attestation_data_root), query.slot], - schema: { - query: {attestation_data_root: Schema.StringRequired, slot: Schema.UintRequired}, + url: "/eth/v1/validator/aggregate_attestation", + method: "GET", + req: { + writeReq: ({attestationDataRoot, slot}) => ({ + query: {attestation_data_root: toHexString(attestationDataRoot), slot}, + }), + parseReq: ({query}) => ({attestationDataRoot: fromHexString(query.attestation_data_root), slot: query.slot}), + schema: { + query: {attestation_data_root: Schema.StringRequired, slot: Schema.UintRequired}, + }, + }, + resp: { + data: ssz.phase0.Attestation, + meta: EmptyMetaCodec, }, }, - - publishAggregateAndProofs: reqOnlyBody(ArrayOf(ssz.phase0.SignedAggregateAndProof), Schema.ObjectArray), - publishContributionAndProofs: reqOnlyBody(ArrayOf(ssz.altair.SignedContributionAndProof), Schema.ObjectArray), - prepareBeaconCommitteeSubnet: reqOnlyBody(ArrayOf(BeaconCommitteeSubscription), Schema.ObjectArray), - prepareSyncCommitteeSubnets: reqOnlyBody(ArrayOf(SyncCommitteeSubscription), Schema.ObjectArray), + publishAggregateAndProofs: { + url: "/eth/v1/validator/aggregate_and_proofs", + method: "POST", + req: { + writeReqJson: ({signedAggregateAndProofs}) => ({ + body: SignedAggregateAndProofListType.toJson(signedAggregateAndProofs), + }), + parseReqJson: ({body}) => ({signedAggregateAndProofs: SignedAggregateAndProofListType.fromJson(body)}), + writeReqSsz: ({signedAggregateAndProofs}) => ({ + body: SignedAggregateAndProofListType.serialize(signedAggregateAndProofs), + }), + parseReqSsz: ({body}) => ({signedAggregateAndProofs: SignedAggregateAndProofListType.deserialize(body)}), + schema: { + body: Schema.ObjectArray, + }, + }, + resp: EmptyResponseCodec, + }, + publishContributionAndProofs: { + url: "/eth/v1/validator/contribution_and_proofs", + method: "POST", + req: { + writeReqJson: ({contributionAndProofs}) => ({ + body: SignedContributionAndProofListType.toJson(contributionAndProofs), + }), + parseReqJson: ({body}) => ({contributionAndProofs: SignedContributionAndProofListType.fromJson(body)}), + writeReqSsz: ({contributionAndProofs}) => ({ + body: SignedContributionAndProofListType.serialize(contributionAndProofs), + }), + parseReqSsz: ({body}) => ({contributionAndProofs: SignedContributionAndProofListType.deserialize(body)}), + schema: { + body: Schema.ObjectArray, + }, + }, + resp: EmptyResponseCodec, + }, + prepareBeaconCommitteeSubnet: { + url: "/eth/v1/validator/beacon_committee_subscriptions", + method: "POST", + req: { + writeReqJson: ({subscriptions}) => ({body: BeaconCommitteeSubscriptionListType.toJson(subscriptions)}), + parseReqJson: ({body}) => ({subscriptions: BeaconCommitteeSubscriptionListType.fromJson(body)}), + writeReqSsz: ({subscriptions}) => ({body: BeaconCommitteeSubscriptionListType.serialize(subscriptions)}), + parseReqSsz: ({body}) => ({subscriptions: BeaconCommitteeSubscriptionListType.deserialize(body)}), + schema: {body: Schema.ObjectArray}, + }, + resp: EmptyResponseCodec, + }, + prepareSyncCommitteeSubnets: { + url: "/eth/v1/validator/sync_committee_subscriptions", + method: "POST", + req: { + writeReqJson: ({subscriptions}) => ({body: SyncCommitteeSubscriptionListType.toJson(subscriptions)}), + parseReqJson: ({body}) => ({subscriptions: SyncCommitteeSubscriptionListType.fromJson(body)}), + writeReqSsz: ({subscriptions}) => ({body: SyncCommitteeSubscriptionListType.serialize(subscriptions)}), + parseReqSsz: ({body}) => ({subscriptions: SyncCommitteeSubscriptionListType.deserialize(body)}), + schema: {body: Schema.ObjectArray}, + }, + resp: EmptyResponseCodec, + }, prepareBeaconProposer: { - writeReq: (items: ProposerPreparationData[]) => ({body: items.map((item) => jsonType("snake").toJson(item))}), - parseReq: ({body}) => [ - (body as Record[]).map((item) => jsonType("snake").fromJson(item) as ProposerPreparationData), - ], - schema: {body: Schema.ObjectArray}, + url: "/eth/v1/validator/prepare_beacon_proposer", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({proposers}) => ({body: ProposerPreparationDataListType.toJson(proposers)}), + parseReqJson: ({body}) => ({proposers: ProposerPreparationDataListType.fromJson(body)}), + schema: {body: Schema.ObjectArray}, + }), + resp: EmptyResponseCodec, }, submitBeaconCommitteeSelections: { - writeReq: (items) => ({body: ArrayOf(BeaconCommitteeSelection).toJson(items)}), - parseReq: () => [[]], + url: "/eth/v1/validator/beacon_committee_selections", + method: "POST", + req: { + writeReqJson: ({selections}) => ({body: BeaconCommitteeSelectionListType.toJson(selections)}), + parseReqJson: ({body}) => ({selections: BeaconCommitteeSelectionListType.fromJson(body)}), + writeReqSsz: ({selections}) => ({body: BeaconCommitteeSelectionListType.serialize(selections)}), + parseReqSsz: ({body}) => ({selections: BeaconCommitteeSelectionListType.deserialize(body)}), + schema: { + body: Schema.ObjectArray, + }, + }, + resp: { + data: BeaconCommitteeSelectionListType, + meta: EmptyMetaCodec, + }, }, submitSyncCommitteeSelections: { - writeReq: (items) => ({body: ArrayOf(SyncCommitteeSelection).toJson(items)}), - parseReq: () => [[]], + url: "/eth/v1/validator/sync_committee_selections", + method: "POST", + req: { + writeReqJson: ({selections}) => ({body: SyncCommitteeSelectionListType.toJson(selections)}), + parseReqJson: ({body}) => ({selections: SyncCommitteeSelectionListType.fromJson(body)}), + writeReqSsz: ({selections}) => ({body: SyncCommitteeSelectionListType.serialize(selections)}), + parseReqSsz: ({body}) => ({selections: SyncCommitteeSelectionListType.deserialize(body)}), + schema: { + body: Schema.ObjectArray, + }, + }, + resp: { + data: SyncCommitteeSelectionListType, + meta: EmptyMetaCodec, + }, }, getLiveness: { - writeReq: (epoch, indexes) => ({params: {epoch}, body: indexes.map((i) => toU64Str(i))}), - parseReq: ({params, body}) => [params.epoch, body.map((i) => fromU64Str(i))], - schema: { - params: {epoch: Schema.UintRequired}, - body: Schema.StringArray, + url: "/eth/v1/validator/liveness/{epoch}", + method: "POST", + req: { + writeReqJson: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.toJson(indices)}), + parseReqJson: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.fromJson(body)}), + writeReqSsz: ({epoch, indices}) => ({params: {epoch}, body: ValidatorIndicesType.serialize(indices)}), + parseReqSsz: ({params, body}) => ({epoch: params.epoch, indices: ValidatorIndicesType.deserialize(body)}), + schema: { + params: {epoch: Schema.UintRequired}, + body: Schema.StringArray, + }, }, - }, - registerValidator: reqOnlyBody(ArrayOf(ssz.bellatrix.SignedValidatorRegistrationV1), Schema.ObjectArray), - }; -} - -export function getReturnTypes(): ReturnTypes { - const rootHexType = new StringType(); - - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const WithDependentRootExecutionOptimistic = (dataType: Type) => - new ContainerType( - { - executionOptimistic: ssz.Boolean, - data: dataType, - dependentRoot: rootHexType, + resp: { + data: LivenessResponseDataListType, + meta: EmptyMetaCodec, }, - {jsonCase: "eth2"} - ); - - const AttesterDuty = new ContainerType( - { - pubkey: ssz.BLSPubkey, - validatorIndex: ssz.ValidatorIndex, - committeeIndex: ssz.CommitteeIndex, - committeeLength: ssz.UintNum64, - committeesAtSlot: ssz.UintNum64, - validatorCommitteeIndex: ssz.UintNum64, - slot: ssz.Slot, }, - {jsonCase: "eth2"} - ); - - const ProposerDuty = new ContainerType( - { - slot: ssz.Slot, - validatorIndex: ssz.ValidatorIndex, - pubkey: ssz.BLSPubkey, - }, - {jsonCase: "eth2"} - ); - - const SyncDuty = new ContainerType( - { - pubkey: ssz.BLSPubkey, - validatorIndex: ssz.ValidatorIndex, - validatorSyncCommitteeIndices: ArrayOf(ssz.UintNum64), - }, - {jsonCase: "eth2"} - ); - - const produceBlockOrContents = WithBlockValues( - WithVersion((fork: ForkName) => - isForkBlobs(fork) ? allForksBlockContentsResSerializer(fork) : ssz[fork].BeaconBlock - ) - ) as TypeJson; - const produceBlindedBlock = WithBlockValues( - WithVersion( - (fork: ForkName) => ssz.allForksBlinded[isForkExecution(fork) ? fork : ForkName.bellatrix].BeaconBlock - ) - ) as TypeJson; - - return { - getAttesterDuties: WithDependentRootExecutionOptimistic(ArrayOf(AttesterDuty)), - getProposerDuties: WithDependentRootExecutionOptimistic(ArrayOf(ProposerDuty)), - getSyncCommitteeDuties: ContainerDataExecutionOptimistic(ArrayOf(SyncDuty)), - - produceBlock: ContainerData(ssz.phase0.BeaconBlock), - produceBlockV2: produceBlockOrContents, - produceBlockV3: { - toJson: (data) => { - if (data.executionPayloadBlinded) { - return { - execution_payload_blinded: true, - execution_payload_source: data.executionPayloadSource, - ...(produceBlindedBlock.toJson(data) as Record), - }; - } else { - return { - execution_payload_blinded: false, - execution_payload_source: data.executionPayloadSource, - ...(produceBlockOrContents.toJson(data) as Record), - }; - } - }, - fromJson: (data) => { - const executionPayloadBlinded = (data as {execution_payload_blinded: boolean}).execution_payload_blinded; - if (executionPayloadBlinded === undefined) { - throw Error(`Invalid executionPayloadBlinded=${executionPayloadBlinded} for fromJson deserialization`); - } - - // extract source from the data and assign defaults in the spec complaint manner if not present in response - const executionPayloadSource = - (data as {execution_payload_source: ProducedBlockSource}).execution_payload_source ?? - (executionPayloadBlinded ? ProducedBlockSource.builder : ProducedBlockSource.engine); - - if (executionPayloadBlinded) { - return {executionPayloadBlinded, executionPayloadSource, ...produceBlindedBlock.fromJson(data)}; - } else { - return {executionPayloadBlinded, executionPayloadSource, ...produceBlockOrContents.fromJson(data)}; - } + registerValidator: { + url: "/eth/v1/validator/register_validator", + method: "POST", + req: { + writeReqJson: ({registrations}) => ({body: SignedValidatorRegistrationV1ListType.toJson(registrations)}), + parseReqJson: ({body}) => ({registrations: SignedValidatorRegistrationV1ListType.fromJson(body)}), + writeReqSsz: ({registrations}) => ({body: SignedValidatorRegistrationV1ListType.serialize(registrations)}), + parseReqSsz: ({body}) => ({registrations: SignedValidatorRegistrationV1ListType.deserialize(body)}), + schema: { + body: Schema.ObjectArray, + }, }, + resp: EmptyResponseCodec, }, - produceBlindedBlock, - - produceAttestationData: ContainerData(ssz.phase0.AttestationData), - produceSyncCommitteeContribution: ContainerData(ssz.altair.SyncCommitteeContribution), - getAggregatedAttestation: ContainerData(ssz.phase0.Attestation), - submitBeaconCommitteeSelections: ContainerData(ArrayOf(BeaconCommitteeSelection)), - submitSyncCommitteeSelections: ContainerData(ArrayOf(SyncCommitteeSelection)), - getLiveness: jsonType("snake"), }; } @@ -797,6 +1002,10 @@ function parseBuilderBoostFactor(builderBoostFactorInput?: string | number | big return builderBoostFactorInput !== undefined ? BigInt(builderBoostFactorInput) : undefined; } +function writeSkipRandaoVerification(skipRandaoVerification?: boolean): string | undefined { + return skipRandaoVerification === true ? "" : undefined; +} + function parseSkipRandaoVerification(skipRandaoVerification?: string): boolean { return skipRandaoVerification !== undefined && skipRandaoVerification === ""; } diff --git a/packages/api/src/beacon/server/beacon.ts b/packages/api/src/beacon/server/beacon.ts index cd1ed72fb586..7a4407243971 100644 --- a/packages/api/src/beacon/server/beacon.ts +++ b/packages/api/src/beacon/server/beacon.ts @@ -1,65 +1,7 @@ import {ChainForkConfig} from "@lodestar/config"; -import {ssz} from "@lodestar/types"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/beacon/index.js"; -import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js"; -import {ServerApi} from "../../interfaces.js"; +import {ApplicationMethods, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes/beacon/index.js"; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - const reqSerializers = getReqSerializers(config); - const returnTypes = getReturnTypes(); - - // Most of routes return JSON, use a server auto-generator - const serverRoutes = getGenericJsonServer, ReqTypes>( - {routesData, getReturnTypes, getReqSerializers}, - config, - api - ); - return { - ...serverRoutes, - // Non-JSON routes. Return JSON or binary depending on "accept" header - getBlock: { - ...serverRoutes.getBlock, - handler: async (req) => { - const response = await api.getBlock(...reqSerializers.getBlock.parseReq(req)); - if (response instanceof Uint8Array) { - // Fastify 4.x.x will automatically add header `Content-Type: application/octet-stream` if TypedArray - return response; - } else { - return returnTypes.getBlock.toJson(response); - } - }, - }, - getBlockV2: { - ...serverRoutes.getBlockV2, - handler: async (req, res) => { - const response = await api.getBlockV2(...reqSerializers.getBlockV2.parseReq(req)); - if (response instanceof Uint8Array) { - const slot = extractSlotFromBlockBytes(response); - const version = config.getForkName(slot); - void res.header("Eth-Consensus-Version", version); - // Fastify 4.x.x will automatically add header `Content-Type: application/octet-stream` if TypedArray - return response; - } else { - void res.header("Eth-Consensus-Version", response.version); - return returnTypes.getBlockV2.toJson(response); - } - }, - }, - }; -} - -function extractSlotFromBlockBytes(block: Uint8Array): number { - const {signature} = ssz.phase0.SignedBeaconBlock.fields; - /** - * class SignedBeaconBlock(Container): - * message: BeaconBlock [offset - 4 bytes] - * signature: BLSSignature [fixed - 96 bytes] - * - * class BeaconBlock(Container): - * slot: Slot [fixed - 8 bytes] - * ... - */ - const offset = 4 + signature.lengthBytes; - const bytes = block.subarray(offset, offset + ssz.Slot.byteLength); - return ssz.Slot.deserialize(bytes); +export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } diff --git a/packages/api/src/beacon/server/config.ts b/packages/api/src/beacon/server/config.ts index 26d78ab9a5f1..09b2cbe717bb 100644 --- a/packages/api/src/beacon/server/config.ts +++ b/packages/api/src/beacon/server/config.ts @@ -1,9 +1,7 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/config.js"; -import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js"; -import {ServerApi} from "../../interfaces.js"; +import {ApplicationMethods, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes/config.js"; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - // All routes return JSON, use a server auto-generator - return getGenericJsonServer, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api); +export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } diff --git a/packages/api/src/beacon/server/debug.ts b/packages/api/src/beacon/server/debug.ts index 553e08afddd0..1508b8ad4178 100644 --- a/packages/api/src/beacon/server/debug.ts +++ b/packages/api/src/beacon/server/debug.ts @@ -1,64 +1,7 @@ import {ChainForkConfig} from "@lodestar/config"; -import {ssz} from "@lodestar/types"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/debug.js"; -import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js"; -import {ServerApi} from "../../interfaces.js"; +import {ApplicationMethods, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes/debug.js"; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - const reqSerializers = getReqSerializers(); - const returnTypes = getReturnTypes(); - - const serverRoutes = getGenericJsonServer, ReqTypes>( - {routesData, getReturnTypes, getReqSerializers}, - config, - api - ); - - return { - ...serverRoutes, - - // Non-JSON routes. Return JSON or binary depending on "accept" header - getState: { - ...serverRoutes.getState, - handler: async (req) => { - const response = await api.getState(...reqSerializers.getState.parseReq(req)); - if (response instanceof Uint8Array) { - // Fastify 4.x.x will automatically add header `Content-Type: application/octet-stream` if TypedArray - return response; - } else { - return returnTypes.getState.toJson(response); - } - }, - }, - getStateV2: { - ...serverRoutes.getStateV2, - handler: async (req, res) => { - const response = await api.getStateV2(...reqSerializers.getStateV2.parseReq(req)); - if (response instanceof Uint8Array) { - const slot = extractSlotFromStateBytes(response); - const version = config.getForkName(slot); - void res.header("Eth-Consensus-Version", version); - // Fastify 4.x.x will automatically add header `Content-Type: application/octet-stream` if TypedArray - return response; - } else { - void res.header("Eth-Consensus-Version", response.version); - return returnTypes.getStateV2.toJson(response); - } - }, - }, - }; -} - -function extractSlotFromStateBytes(state: Uint8Array): number { - const {genesisTime, genesisValidatorsRoot} = ssz.phase0.BeaconState.fields; - /** - * class BeaconState(Container): - * genesisTime: BeaconBlock [offset - 4 bytes] - * genesisValidatorsRoot: BLSSignature [fixed - 96 bytes] - * slot: Slot [fixed - 8 bytes] - * ... - */ - const offset = genesisTime.byteLength + genesisValidatorsRoot.lengthBytes; - const bytes = state.subarray(offset, offset + ssz.Slot.byteLength); - return ssz.Slot.deserialize(bytes); +export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } diff --git a/packages/api/src/beacon/server/events.ts b/packages/api/src/beacon/server/events.ts index 6d780ed1bf70..cbeae24f6908 100644 --- a/packages/api/src/beacon/server/events.ts +++ b/packages/api/src/beacon/server/events.ts @@ -1,17 +1,15 @@ -import {Api, ReqTypes, routesData, getEventSerdes, eventTypes} from "../routes/events.js"; -import {ApiError, ServerRoutes} from "../../utils/server/index.js"; -import {ServerApi} from "../../interfaces.js"; +import {ChainForkConfig} from "@lodestar/config"; +import {ApiError, ApplicationMethods, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions, eventTypes, getEventSerdes} from "../routes/events.js"; -export function getRoutes(api: ServerApi): ServerRoutes { +export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods): FastifyRoutes { const eventSerdes = getEventSerdes(); + const serverRoutes = createFastifyRoutes(getDefinitions(config), methods); return { // Non-JSON route. Server Sent Events (SSE) eventstream: { - url: routesData.eventstream.url, - method: routesData.eventstream.method, - id: "eventstream", - + ...serverRoutes.eventstream, handler: async (req, res) => { const validTopics = new Set(Object.values(eventTypes)); for (const topic of req.query.topics) { @@ -40,13 +38,17 @@ export function getRoutes(api: ServerApi): ServerRoutes { res.raw.setHeader("X-Accel-Buffering", "no"); await new Promise((resolve, reject) => { - void api.eventstream(req.query.topics, controller.signal, (event) => { - try { - const data = eventSerdes.toJson(event); - res.raw.write(serializeSSEEvent({event: event.type, data})); - } catch (e) { - reject(e as Error); - } + void methods.eventstream({ + topics: req.query.topics, + signal: controller.signal, + onEvent: (event) => { + try { + const data = eventSerdes.toJson(event); + res.raw.write(serializeSSEEvent({event: event.type, data})); + } catch (e) { + reject(e); + } + }, }); // The stream will never end by the server unless the node is stopped. @@ -62,16 +64,6 @@ export function getRoutes(api: ServerApi): ServerRoutes { controller.abort(); } }, - - // TODO: Bundle this in /routes/events? - schema: { - querystring: { - type: "object", - properties: { - topics: {type: "array", items: {type: "string"}}, - }, - }, - }, }, }; } diff --git a/packages/api/src/beacon/server/index.ts b/packages/api/src/beacon/server/index.ts index da77dfad32af..21c0607d3f14 100644 --- a/packages/api/src/beacon/server/index.ts +++ b/packages/api/src/beacon/server/index.ts @@ -1,8 +1,8 @@ +import type {FastifyInstance} from "fastify"; import {ChainForkConfig} from "@lodestar/config"; -import {Api} from "../routes/index.js"; -import {ApiError, ServerInstance, ServerRoute, RouteConfig, registerRoute} from "../../utils/server/index.js"; +import {ApplicationMethods, FastifyRoute} from "../../utils/server/index.js"; +import {Endpoints} from "../routes/index.js"; -import {ServerApi} from "../../interfaces.js"; import * as beacon from "./beacon.js"; import * as configApi from "./config.js"; import * as debug from "./debug.js"; @@ -13,36 +13,32 @@ import * as node from "./node.js"; import * as proof from "./proof.js"; import * as validator from "./validator.js"; -// Re-export for usage in beacon-node -export {ApiError}; - -// Re-export for convenience -export type {RouteConfig}; +export type BeaconApiMethods = {[K in keyof Endpoints]: ApplicationMethods}; export function registerRoutes( - server: ServerInstance, + server: FastifyInstance, config: ChainForkConfig, - api: {[K in keyof Api]: ServerApi}, - enabledNamespaces: (keyof Api)[] + methods: BeaconApiMethods, + enabledNamespaces: (keyof Endpoints)[] ): void { const routesByNamespace: { - // Enforces that we are declaring routes for every routeId in `Api` - [K in keyof Api]: () => { - // The ReqTypes are enforced in each getRoutes return type + // Enforces that we are declaring routes for every routeId in `Endpoints` + [K in keyof Endpoints]: () => { + // The Endpoints are enforced in each getRoutes return type // eslint-disable-next-line @typescript-eslint/no-explicit-any - [K2 in keyof Api[K]]: ServerRoute; + [K2 in keyof Endpoints[K]]: FastifyRoute; }; } = { // Initializes route types and their definitions - beacon: () => beacon.getRoutes(config, api.beacon), - config: () => configApi.getRoutes(config, api.config), - debug: () => debug.getRoutes(config, api.debug), - events: () => events.getRoutes(api.events), - lightclient: () => lightclient.getRoutes(config, api.lightclient), - lodestar: () => lodestar.getRoutes(config, api.lodestar), - node: () => node.getRoutes(config, api.node), - proof: () => proof.getRoutes(config, api.proof), - validator: () => validator.getRoutes(config, api.validator), + beacon: () => beacon.getRoutes(config, methods.beacon), + config: () => configApi.getRoutes(config, methods.config), + debug: () => debug.getRoutes(config, methods.debug), + events: () => events.getRoutes(config, methods.events), + lightclient: () => lightclient.getRoutes(config, methods.lightclient), + lodestar: () => lodestar.getRoutes(config, methods.lodestar), + node: () => node.getRoutes(config, methods.node), + proof: () => proof.getRoutes(config, methods.proof), + validator: () => validator.getRoutes(config, methods.validator), }; for (const namespace of enabledNamespaces) { @@ -52,7 +48,9 @@ export function registerRoutes( } for (const route of Object.values(routes())) { - registerRoute(server, route, namespace); + // Append the namespace as a tag for downstream consumption + route.schema.tags = [namespace]; + server.route(route); } } } diff --git a/packages/api/src/beacon/server/lightclient.ts b/packages/api/src/beacon/server/lightclient.ts index d587ec29823d..a991ccc7cf9f 100644 --- a/packages/api/src/beacon/server/lightclient.ts +++ b/packages/api/src/beacon/server/lightclient.ts @@ -1,9 +1,7 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/lightclient.js"; -import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js"; -import {ServerApi} from "../../interfaces.js"; +import {ApplicationMethods, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes/lightclient.js"; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - // All routes return JSON, use a server auto-generator - return getGenericJsonServer, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api); +export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } diff --git a/packages/api/src/beacon/server/lodestar.ts b/packages/api/src/beacon/server/lodestar.ts index b52ed9832f69..ba65c8853997 100644 --- a/packages/api/src/beacon/server/lodestar.ts +++ b/packages/api/src/beacon/server/lodestar.ts @@ -1,9 +1,7 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/lodestar.js"; -import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js"; -import {ServerApi} from "../../interfaces.js"; +import {ApplicationMethods, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes/lodestar.js"; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - // All routes return JSON, use a server auto-generator - return getGenericJsonServer, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api); +export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } diff --git a/packages/api/src/beacon/server/node.ts b/packages/api/src/beacon/server/node.ts index 8ee3acdb426b..7914bc0a7127 100644 --- a/packages/api/src/beacon/server/node.ts +++ b/packages/api/src/beacon/server/node.ts @@ -1,29 +1,7 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/node.js"; -import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js"; -import {ServerApi} from "../../interfaces.js"; +import {ApplicationMethods, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes/node.js"; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes, ReqTypes> { - const reqSerializers = getReqSerializers(); - - const serverRoutes = getGenericJsonServer, ReqTypes>( - {routesData, getReturnTypes, getReqSerializers}, - config, - api - ); - - return { - ...serverRoutes, - - getHealth: { - ...serverRoutes.getHealth, - handler: async (req, res) => { - const args = reqSerializers.getHealth.parseReq(req); - // Note: This type casting is required as per route definition getHealth - // does not return a value but since the internal API does not have access - // to response object it is required to set the HTTP status code here. - res.statusCode = (await api.getHealth(...args)) as unknown as number; - }, - }, - }; +export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } diff --git a/packages/api/src/beacon/server/proof.ts b/packages/api/src/beacon/server/proof.ts index bc865626ba72..d4f247fad525 100644 --- a/packages/api/src/beacon/server/proof.ts +++ b/packages/api/src/beacon/server/proof.ts @@ -1,46 +1,7 @@ -import {CompactMultiProof} from "@chainsafe/persistent-merkle-tree"; import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/proof.js"; -import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js"; -import {ServerApi} from "../../interfaces.js"; +import {ApplicationMethods, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes/proof.js"; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - const reqSerializers = getReqSerializers(); - const serverRoutes = getGenericJsonServer, ReqTypes>( - {routesData, getReturnTypes, getReqSerializers}, - config, - api - ); - - return { - // Non-JSON routes. Return binary - getStateProof: { - ...serverRoutes.getStateProof, - handler: async (req) => { - const args = reqSerializers.getStateProof.parseReq(req); - const {data} = await api.getStateProof(...args); - const leaves = (data as CompactMultiProof).leaves; - const response = new Uint8Array(32 * leaves.length); - for (let i = 0; i < leaves.length; i++) { - response.set(leaves[i], i * 32); - } - // Fastify 4.x.x will automatically add header `Content-Type: application/octet-stream` if TypedArray - return response; - }, - }, - getBlockProof: { - ...serverRoutes.getBlockProof, - handler: async (req) => { - const args = reqSerializers.getBlockProof.parseReq(req); - const {data} = await api.getBlockProof(...args); - const leaves = (data as CompactMultiProof).leaves; - const response = new Uint8Array(32 * leaves.length); - for (let i = 0; i < leaves.length; i++) { - response.set(leaves[i], i * 32); - } - // Fastify 4.x.x will automatically add header `Content-Type: application/octet-stream` if TypedArray - return response; - }, - }, - }; +export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } diff --git a/packages/api/src/beacon/server/validator.ts b/packages/api/src/beacon/server/validator.ts index 5d6c22557060..01e6502857a4 100644 --- a/packages/api/src/beacon/server/validator.ts +++ b/packages/api/src/beacon/server/validator.ts @@ -1,31 +1,7 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes/validator.js"; -import {ServerRoutes, getGenericJsonServer} from "../../utils/server/index.js"; -import {ServerApi} from "../../interfaces.js"; +import {ApplicationMethods, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes/validator.js"; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - const reqSerializers = getReqSerializers(); - const returnTypes = getReturnTypes(); - - // Most of routes return JSON, use a server auto-generator - const serverRoutes = getGenericJsonServer, ReqTypes>( - {routesData, getReturnTypes, getReqSerializers}, - config, - api - ); - return { - ...serverRoutes, - produceBlockV3: { - ...serverRoutes.produceBlockV3, - handler: async (req, res) => { - const response = await api.produceBlockV3(...reqSerializers.produceBlockV3.parseReq(req)); - void res.header("Eth-Consensus-Version", response.version); - void res.header("Eth-Execution-Payload-Blinded", response.executionPayloadBlinded); - void res.header("Eth-Execution-Payload-Value", response.executionPayloadValue); - void res.header("Eth-Consensus-Block-Value", response.consensusBlockValue); - - return returnTypes.produceBlockV3.toJson(response); - }, - }, - }; +export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } diff --git a/packages/api/src/builder/client.ts b/packages/api/src/builder/client.ts index 381732b81433..f3d12fcb353a 100644 --- a/packages/api/src/builder/client.ts +++ b/packages/api/src/builder/client.ts @@ -1,13 +1,9 @@ import {ChainForkConfig} from "@lodestar/config"; -import {IHttpClient, generateGenericJsonClient, ApiWithExtraOpts} from "../utils/client/index.js"; -import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "./routes.js"; +import {ApiClientMethods, IHttpClient, createApiClientMethods} from "../utils/client/index.js"; +import {Endpoints, getDefinitions} from "./routes.js"; -/** - * REST HTTP client for builder routes - */ -export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiWithExtraOpts { - const reqSerializers = getReqSerializers(config); - const returnTypes = getReturnTypes(); - // All routes return JSON, use a client auto-generator - return generateGenericJsonClient(routesData, reqSerializers, returnTypes, httpClient); +export type ApiClient = ApiClientMethods; + +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/builder/index.ts b/packages/api/src/builder/index.ts index 531ceac1b624..d1d9d31a17ab 100644 --- a/packages/api/src/builder/index.ts +++ b/packages/api/src/builder/index.ts @@ -1,25 +1,25 @@ import {ChainForkConfig} from "@lodestar/config"; -import { - HttpClient, - HttpClientModules, - HttpClientOptions, - IHttpClient, - ApiWithExtraOpts, -} from "../utils/client/index.js"; -import {Api as BuilderApi} from "../builder/routes.js"; +import {HttpClient, HttpClientModules, HttpClientOptions, IHttpClient} from "../utils/client/httpClient.js"; +import {Endpoints} from "./routes.js"; +import type {ApiClient} from "./client.js"; + import * as builder from "./client.js"; // NOTE: Don't export server here so it's not bundled to all consumers -// Note: build API does not have namespaces as routes are declared at the "root" namespace +export type {ApiClient, Endpoints}; + +// Note: builder API does not have namespaces as routes are declared at the "root" namespace -export type Api = ApiWithExtraOpts; type ClientModules = HttpClientModules & { config: ChainForkConfig; httpClient?: IHttpClient; }; -export function getClient(opts: HttpClientOptions, modules: ClientModules): Api { +/** + * REST HTTP client for builder routes + */ +export function getClient(opts: HttpClientOptions, modules: ClientModules): ApiClient { const {config} = modules; const httpClient = modules.httpClient ?? new HttpClient(opts, modules); diff --git a/packages/api/src/builder/routes.ts b/packages/api/src/builder/routes.ts index ca4c81a9fade..d703085583da 100644 --- a/packages/api/src/builder/routes.ts +++ b/packages/api/src/builder/routes.ts @@ -1,99 +1,152 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import {fromHexString, toHexString} from "@chainsafe/ssz"; import {ssz, allForks, bellatrix, Slot, Root, BLSPubkey} from "@lodestar/types"; -import {ForkName, isForkExecution, isForkBlobs} from "@lodestar/params"; +import {ForkName, isForkBlobs} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; +import {Endpoint, RouteDefinitions, Schema} from "../utils/index.js"; +import {MetaHeader, VersionCodec, VersionMeta} from "../utils/metadata.js"; import { - ReturnTypes, - RoutesData, - Schema, - ReqSerializers, - reqOnlyBody, - reqEmpty, - ReqEmpty, ArrayOf, + EmptyArgs, + EmptyRequestCodec, + EmptyMeta, + EmptyRequest, + EmptyResponseCodec, + EmptyResponseData, + JsonOnlyReq, WithVersion, -} from "../utils/index.js"; +} from "../utils/codecs.js"; +import {getBlindedForkTypes, getExecutionForkTypes, toForkName} from "../utils/fork.js"; +import {fromHeaders} from "../utils/headers.js"; + // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes -import {getReqSerializers as getBeaconReqSerializers} from "../beacon/routes/beacon/block.js"; -import {HttpStatusCode} from "../utils/client/httpStatusCode.js"; -import {ApiClientResponse} from "../interfaces.js"; -export type Api = { - status(): Promise>; - registerValidator( - registrations: bellatrix.SignedValidatorRegistrationV1[] - ): Promise>; - getHeader( - slot: Slot, - parentHash: Root, - proposerPubKey: BLSPubkey - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: allForks.SignedBuilderBid; version: ForkName}}, - HttpStatusCode.NOT_FOUND | HttpStatusCode.BAD_REQUEST - > +// Mev-boost might not return any data if there are no bids from builders or min-bid threshold was not reached. +// In this case, we receive a success response (204) which is not handled as an error. The generic response +// handler already checks the status code and will not attempt to parse the body, but it will return no value. +// It is important that this type indicates that there might be no value to ensure it is properly handled downstream. +export type MaybeSignedBuilderBid = allForks.SignedBuilderBid | undefined; + +const RegistrationsType = ArrayOf(ssz.bellatrix.SignedValidatorRegistrationV1); + +export type Endpoints = { + status: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + EmptyResponseData, + EmptyMeta >; - submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlock): Promise< - ApiClientResponse< - { - [HttpStatusCode.OK]: { - data: allForks.ExecutionPayload | allForks.ExecutionPayloadAndBlobsBundle; - version: ForkName; - }; - }, - HttpStatusCode.SERVICE_UNAVAILABLE - > + + registerValidator: Endpoint< + "POST", + {registrations: bellatrix.SignedValidatorRegistrationV1[]}, + {body: unknown}, + EmptyResponseData, + EmptyMeta >; -}; -/** - * Define javascript values for each route - */ -export const routesData: RoutesData = { - status: {url: "/eth/v1/builder/status", method: "GET"}, - registerValidator: {url: "/eth/v1/builder/validators", method: "POST"}, - getHeader: {url: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}", method: "GET"}, - submitBlindedBlock: {url: "/eth/v1/builder/blinded_blocks", method: "POST"}, -}; + getHeader: Endpoint< + "GET", + { + slot: Slot; + parentHash: Root; + proposerPubkey: BLSPubkey; + }, + {params: {slot: Slot; parent_hash: string; pubkey: string}}, + MaybeSignedBuilderBid, + VersionMeta + >; -/* eslint-disable @typescript-eslint/naming-convention */ -export type ReqTypes = { - status: ReqEmpty; - registerValidator: {body: unknown}; - getHeader: {params: {slot: Slot; parent_hash: string; pubkey: string}}; - submitBlindedBlock: {body: unknown}; + submitBlindedBlock: Endpoint< + "POST", + {signedBlindedBlock: allForks.SignedBlindedBeaconBlock}, + {body: unknown; headers: {[MetaHeader.Version]: string}}, + allForks.ExecutionPayload | allForks.ExecutionPayloadAndBlobsBundle, + VersionMeta + >; }; -export function getReqSerializers(config: ChainForkConfig): ReqSerializers { +// NOTE: Builder API does not support SSZ as per spec, need to keep routes as JSON-only for now +// See https://github.com/ethereum/builder-specs/issues/53 for more details + +export function getDefinitions(config: ChainForkConfig): RouteDefinitions { return { - status: reqEmpty, - registerValidator: reqOnlyBody(ArrayOf(ssz.bellatrix.SignedValidatorRegistrationV1), Schema.ObjectArray), + status: { + url: "/eth/v1/builder/status", + method: "GET", + req: EmptyRequestCodec, + resp: EmptyResponseCodec, + }, + registerValidator: { + url: "/eth/v1/builder/validators", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({registrations}) => ({body: RegistrationsType.toJson(registrations)}), + parseReqJson: ({body}) => ({registrations: RegistrationsType.fromJson(body)}), + schema: {body: Schema.ObjectArray}, + }), + resp: EmptyResponseCodec, + }, getHeader: { - writeReq: (slot, parentHash, proposerPubKey) => ({ - params: {slot, parent_hash: toHexString(parentHash), pubkey: toHexString(proposerPubKey)}, + url: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}", + method: "GET", + req: { + writeReq: ({slot, parentHash, proposerPubkey: proposerPubKey}) => ({ + params: {slot, parent_hash: toHexString(parentHash), pubkey: toHexString(proposerPubKey)}, + }), + parseReq: ({params}) => ({ + slot: params.slot, + parentHash: fromHexString(params.parent_hash), + proposerPubkey: fromHexString(params.pubkey), + }), + schema: { + params: {slot: Schema.UintRequired, parent_hash: Schema.StringRequired, pubkey: Schema.StringRequired}, + }, + }, + resp: { + data: WithVersion( + (fork: ForkName) => getExecutionForkTypes(fork).SignedBuilderBid + ), + meta: VersionCodec, + }, + }, + submitBlindedBlock: { + url: "/eth/v1/builder/blinded_blocks", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({signedBlindedBlock}) => { + const fork = config.getForkName(signedBlindedBlock.message.slot); + return { + body: getBlindedForkTypes(fork).SignedBeaconBlock.toJson(signedBlindedBlock), + headers: { + [MetaHeader.Version]: fork, + }, + }; + }, + parseReqJson: ({body, headers}) => { + const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); + return { + signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.fromJson(body), + }; + }, + schema: { + body: Schema.Object, + headers: {[MetaHeader.Version]: Schema.String}, + }, }), - parseReq: ({params}) => [params.slot, fromHexString(params.parent_hash), fromHexString(params.pubkey)], - schema: { - params: {slot: Schema.UintRequired, parent_hash: Schema.StringRequired, pubkey: Schema.StringRequired}, + resp: { + data: WithVersion( + (fork: ForkName) => { + return isForkBlobs(fork) + ? ssz.allForksBlobs[fork].ExecutionPayloadAndBlobsBundle + : getExecutionForkTypes(fork).ExecutionPayload; + } + ), + meta: VersionCodec, }, }, - submitBlindedBlock: getBeaconReqSerializers(config)["publishBlindedBlock"], - }; -} - -export function getReturnTypes(): ReturnTypes { - return { - getHeader: WithVersion((fork: ForkName) => - isForkExecution(fork) ? ssz.allForksExecution[fork].SignedBuilderBid : ssz.bellatrix.SignedBuilderBid - ), - submitBlindedBlock: WithVersion( - (fork: ForkName) => - isForkBlobs(fork) - ? ssz.allForksBlobs[fork].ExecutionPayloadAndBlobsBundle - : isForkExecution(fork) - ? ssz.allForksExecution[fork].ExecutionPayload - : ssz.bellatrix.ExecutionPayload - ), }; } diff --git a/packages/api/src/builder/server/index.ts b/packages/api/src/builder/server/index.ts index 0b51be896258..888d6bb64bec 100644 --- a/packages/api/src/builder/server/index.ts +++ b/packages/api/src/builder/server/index.ts @@ -1,26 +1,19 @@ +import type {FastifyInstance} from "fastify"; import {ChainForkConfig} from "@lodestar/config"; -import {ServerApi} from "../../interfaces.js"; -import { - ServerInstance, - ServerRoutes, - getGenericJsonServer, - registerRoute, - type RouteConfig, -} from "../../utils/server/index.js"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes.js"; +import {ApplicationMethods, FastifyRoute, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes.js"; +import {AnyEndpoint} from "../../utils/codecs.js"; -// Re-export for convenience -export type {RouteConfig}; +export type BuilderApiMethods = ApplicationMethods; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - // All routes return JSON, use a server auto-generator - return getGenericJsonServer, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api); +export function getRoutes(config: ChainForkConfig, methods: BuilderApiMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } -export function registerRoutes(server: ServerInstance, config: ChainForkConfig, api: ServerApi): void { - const routes = getRoutes(config, api); +export function registerRoutes(server: FastifyInstance, config: ChainForkConfig, methods: BuilderApiMethods): void { + const routes = getRoutes(config, methods); for (const route of Object.values(routes)) { - registerRoute(server, route); + server.route(route as FastifyRoute); } } diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 27ef2ada69d3..a93fc83d4628 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,10 +1,18 @@ // Re-exporting beacon only for backwards compatibility export * from "./beacon/index.js"; -export * from "./interfaces.js"; -export {HttpStatusCode} from "./utils/client/httpStatusCode.js"; -export type {HttpErrorCodes, HttpSuccessCodes} from "./utils/client/httpStatusCode.js"; -export {HttpClient, HttpError, ApiError, FetchError, isFetchError, fetch} from "./utils/client/index.js"; -export type {IHttpClient, HttpClientOptions, HttpClientModules, Metrics} from "./utils/client/index.js"; -export * from "./utils/routes.js"; +export {HttpStatusCode} from "./utils/httpStatusCode.js"; +export {WireFormat} from "./utils/wireFormat.js"; +export type {HttpErrorCodes, HttpSuccessCodes} from "./utils/httpStatusCode.js"; +export {ApiResponse, HttpClient, FetchError, isFetchError, fetch, defaultInit} from "./utils/client/index.js"; +export type {ApiRequestInit} from "./utils/client/request.js"; +export type {Endpoint} from "./utils/types.js"; +export type { + ApiClientMethods, + IHttpClient, + HttpClientOptions, + HttpClientModules, + Metrics, +} from "./utils/client/index.js"; +export {ApiError} from "./utils/client/error.js"; // NOTE: Don't export server here so it's not bundled to all consumers diff --git a/packages/api/src/interfaces.ts b/packages/api/src/interfaces.ts deleted file mode 100644 index 4d57bcbc8934..000000000000 --- a/packages/api/src/interfaces.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {HttpStatusCode, HttpSuccessCodes} from "./utils/client/httpStatusCode.js"; -import {Resolves} from "./utils/types.js"; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -export type ResponseFormat = "json" | "ssz"; -export type APIClientHandler = (...args: any) => PromiseLike; -export type APIServerHandler = (...args: any) => PromiseLike; - -export type ApiClientSuccessResponse = {ok: true; status: S; response: T; error?: never}; -export type ApiClientErrorResponse> = { - ok: false; - status: S; - response?: never; - error: {code: S; operationId: string; message?: string}; -}; -export type ApiClientResponse< - S extends Partial<{[K in HttpSuccessCodes]: unknown}> = {[K in HttpSuccessCodes]: unknown}, - E extends Exclude = Exclude, -> = - | {[K in keyof S]: ApiClientSuccessResponse}[keyof S] - | {[K in E]: ApiClientErrorResponse}[E] - | ApiClientErrorResponse; - -export type ApiClientResponseData = T extends {ok: true; response: infer R} ? R : never; - -export type GenericOptions = Record; - -export type ServerApi> = { - [K in keyof T]: ( - ...args: [...args: Parameters, opts?: GenericOptions] - ) => Promise>>; -}; - -export type ClientApi> = { - [K in keyof T]: (...args: Parameters) => Promise}>>; -}; diff --git a/packages/api/src/keymanager/client.ts b/packages/api/src/keymanager/client.ts index 4df13d383d7c..f3d12fcb353a 100644 --- a/packages/api/src/keymanager/client.ts +++ b/packages/api/src/keymanager/client.ts @@ -1,10 +1,9 @@ import {ChainForkConfig} from "@lodestar/config"; -import {IHttpClient, generateGenericJsonClient} from "../utils/client/index.js"; -import {Api, ReqTypes, routesData, getReqSerializers, getReturnTypes} from "./routes.js"; +import {ApiClientMethods, IHttpClient, createApiClientMethods} from "../utils/client/index.js"; +import {Endpoints, getDefinitions} from "./routes.js"; -export function getClient(_config: ChainForkConfig, httpClient: IHttpClient): Api { - const reqSerializers = getReqSerializers(); - const returnTypes = getReturnTypes(); - // All routes return JSON, use a client auto-generator - return generateGenericJsonClient(routesData, reqSerializers, returnTypes, httpClient); +export type ApiClient = ApiClientMethods; + +export function getClient(config: ChainForkConfig, httpClient: IHttpClient): ApiClient { + return createApiClientMethods(getDefinitions(config), httpClient); } diff --git a/packages/api/src/keymanager/index.ts b/packages/api/src/keymanager/index.ts index 3232876e2657..1ffcaef897eb 100644 --- a/packages/api/src/keymanager/index.ts +++ b/packages/api/src/keymanager/index.ts @@ -1,13 +1,26 @@ import {ChainForkConfig} from "@lodestar/config"; -import {} from "../beacon/client/index.js"; import {IHttpClient, HttpClient, HttpClientModules, HttpClientOptions} from "../utils/client/index.js"; -import {Api} from "./routes.js"; +import type {ApiClient} from "./client.js"; import * as keymanager from "./client.js"; // NOTE: Don't export server here so it's not bundled to all consumers export {ImportStatus, DeletionStatus, ImportRemoteKeyStatus, DeleteRemoteKeyStatus} from "./routes.js"; -export type {ResponseStatus, SignerDefinition, KeystoreStr, SlashingProtectionData, PubkeyHex, Api} from "./routes.js"; +export type { + ResponseStatus, + SignerDefinition, + RemoteSignerDefinition, + KeystoreStr, + SlashingProtectionData, + PubkeyHex, + Endpoints, + FeeRecipientData, + GraffitiData, + GasLimitData, + BuilderBoostFactorData, +} from "./routes.js"; + +export type {ApiClient}; type ClientModules = HttpClientModules & { config: ChainForkConfig; @@ -15,9 +28,9 @@ type ClientModules = HttpClientModules & { }; /** - * REST HTTP client for all keymanager routes + * REST HTTP client for keymanager routes */ -export function getClient(opts: HttpClientOptions, modules: ClientModules): Api { +export function getClient(opts: HttpClientOptions, modules: ClientModules): ApiClient { const {config} = modules; const httpClient = modules.httpClient ?? new HttpClient(opts, modules); diff --git a/packages/api/src/keymanager/routes.ts b/packages/api/src/keymanager/routes.ts index 48f928e86100..b6abe8928d77 100644 --- a/packages/api/src/keymanager/routes.ts +++ b/packages/api/src/keymanager/routes.ts @@ -1,17 +1,20 @@ -import {ContainerType} from "@chainsafe/ssz"; +/* eslint-disable @typescript-eslint/naming-convention */ +import {ContainerType, ValueOf} from "@chainsafe/ssz"; +import {ChainForkConfig} from "@lodestar/config"; import {Epoch, phase0, ssz, stringType} from "@lodestar/types"; -import {ApiClientResponse} from "../interfaces.js"; -import {HttpStatusCode} from "../utils/client/httpStatusCode.js"; +import {Schema, Endpoint, RouteDefinitions} from "../utils/index.js"; +import {WireFormat} from "../utils/wireFormat.js"; import { - ReturnTypes, - RoutesData, - Schema, - reqEmpty, - ReqSerializers, - ReqEmpty, - jsonType, - ContainerData, -} from "../utils/index.js"; + EmptyArgs, + EmptyRequestCodec, + EmptyMeta, + EmptyMetaCodec, + EmptyRequest, + EmptyResponseCodec, + EmptyResponseData, + JsonOnlyReq, + JsonOnlyResponseCodec, +} from "../utils/codecs.js"; export enum ImportStatus { /** Keystore successfully decrypted and imported to keymanager permanent storage */ @@ -60,22 +63,39 @@ export type ResponseStatus = { message?: string; }; -export type FeeRecipientData = { - pubkey: string; - ethaddress: string; -}; -export type GraffitiData = { - pubkey: string; - graffiti: string; -}; -export type GasLimitData = { - pubkey: string; - gasLimit: number; -}; -export type BuilderBoostFactorData = { - pubkey: string; - builderBoostFactor: bigint; -}; +export const FeeRecipientDataType = new ContainerType( + { + pubkey: stringType, + ethaddress: stringType, + }, + {jsonCase: "eth2"} +); +export const GraffitiDataType = new ContainerType( + { + pubkey: stringType, + graffiti: stringType, + }, + {jsonCase: "eth2"} +); +export const GasLimitDataType = new ContainerType( + { + pubkey: stringType, + gasLimit: ssz.UintNum64, + }, + {jsonCase: "eth2"} +); +export const BuilderBoostFactorDataType = new ContainerType( + { + pubkey: stringType, + builderBoostFactor: ssz.UintBn64, + }, + {jsonCase: "eth2"} +); + +export type FeeRecipientData = ValueOf; +export type GraffitiData = ValueOf; +export type GasLimitData = ValueOf; +export type BuilderBoostFactorData = ValueOf; export type SignerDefinition = { pubkey: PubkeyHex; @@ -88,6 +108,8 @@ export type SignerDefinition = { readonly: boolean; }; +export type RemoteSignerDefinition = Pick; + /** * JSON serialized representation of a single keystore in EIP-2335: BLS12-381 Keystore format. * ``` @@ -112,24 +134,40 @@ export type SlashingProtectionData = string; */ export type PubkeyHex = string; -export type Api = { +/** + * An address on the execution (Ethereum 1) network. + * ``` + * "0xAbcF8e0d4e9587369b2301D0790347320302cc09" + * ``` + */ +export type EthAddress = string; + +/** + * Arbitrary data to set in the graffiti field of BeaconBlockBody + * ``` + * "plain text value" + * ``` + */ +export type Graffiti = string; + +export type Endpoints = { /** * List all validating pubkeys known to and decrypted by this keymanager binary * * https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml */ - listKeys(): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: { - data: { - validatingPubkey: PubkeyHex; - /** The derivation path (if present in the imported keystore) */ - derivationPath?: string; - /** The key associated with this pubkey cannot be deleted from the API */ - readonly?: boolean; - }[]; - }; - }> + listKeys: Endpoint< + "GET", + EmptyArgs, + EmptyRequest, + { + validatingPubkey: PubkeyHex; + /** The derivation path (if present in the imported keystore) */ + derivationPath?: string; + /** The key associated with this pubkey cannot be deleted from the API */ + readonly?: boolean; + }[], + EmptyMeta >; /** @@ -138,18 +176,24 @@ export type Api = { * Users SHOULD send slashing_protection data associated with the imported pubkeys. MUST follow the format defined in * EIP-3076: Slashing Protection Interchange Format. * - * @param keystores JSON-encoded keystore files generated with the Launchpad - * @param passwords Passwords to unlock imported keystore files. `passwords[i]` must unlock `keystores[i]` - * @param slashingProtection Slashing protection data for some of the keys of `keystores` - * @returns Status result of each `request.keystores` with same length and order of `request.keystores` + * Returns status result of each `request.keystores` with same length and order of `request.keystores` * * https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml */ - importKeystores( - keystoresStr: KeystoreStr[], - passwords: string[], - slashingProtectionStr?: SlashingProtectionData - ): Promise[]}}>>; + importKeystores: Endpoint< + "POST", + { + /** JSON-encoded keystore files generated with the Launchpad */ + keystores: KeystoreStr[]; + /** Passwords to unlock imported keystore files. `passwords[i]` must unlock `keystores[i]` */ + passwords: string[]; + /** Slashing protection data for some of the keys of `keystores` */ + slashingProtection?: SlashingProtectionData; + }, + {body: {keystores: KeystoreStr[]; passwords: string[]; slashing_protection?: SlashingProtectionData}}, + ResponseStatus[], + EmptyMeta + >; /** * DELETE must delete all keys from `request.pubkeys` that are known to the keymanager and exist in its @@ -167,109 +211,150 @@ export type Api = { * Slashing protection data must only be returned for keys from `request.pubkeys` for which a * `deleted` or `not_active` status is returned. * - * @param pubkeys List of public keys to delete. - * @returns Deletion status of all keys in `request.pubkeys` in the same order. + * Returns deletion status of all keys in `request.pubkeys` in the same order. * * https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml */ - deleteKeys(pubkeysHex: string[]): Promise< - ApiClientResponse<{ - [HttpStatusCode.OK]: {data: ResponseStatus[]; slashingProtection: SlashingProtectionData}; - }> + deleteKeys: Endpoint< + "DELETE", + { + /** List of public keys to delete */ + pubkeys: PubkeyHex[]; + }, + {body: {pubkeys: string[]}}, + {statuses: ResponseStatus[]; slashingProtection: SlashingProtectionData}, + EmptyMeta >; /** * List all remote validating pubkeys known to this validator client binary */ - listRemoteKeys(): Promise>; + listRemoteKeys: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + SignerDefinition[], + EmptyMeta + >; /** * Import remote keys for the validator client to request duties for */ - importRemoteKeys( - remoteSigners: Pick[] - ): Promise[]}}>>; - - deleteRemoteKeys( - pubkeys: PubkeyHex[] - ): Promise[]}}>>; - - listFeeRecipient(pubkey: string): Promise>; - setFeeRecipient( - pubkey: string, - ethaddress: string - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void}, - HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND - > + importRemoteKeys: Endpoint< + "POST", + {remoteSigners: RemoteSignerDefinition[]}, + {body: {remote_keys: RemoteSignerDefinition[]}}, + ResponseStatus[], + EmptyMeta + >; + + /** + * DELETE must delete all keys from `request.pubkeys` that are known to the validator client and exist in its + * persistent storage. + * + * DELETE should never return a 404 response, even if all pubkeys from `request.pubkeys` have no existing keystores. + */ + deleteRemoteKeys: Endpoint< + "DELETE", + {pubkeys: PubkeyHex[]}, + {body: {pubkeys: string[]}}, + ResponseStatus[], + EmptyMeta + >; + + listFeeRecipient: Endpoint< + // ⏎ + "GET", + {pubkey: PubkeyHex}, + {params: {pubkey: string}}, + FeeRecipientData, + EmptyMeta + >; + setFeeRecipient: Endpoint< + "POST", + {pubkey: PubkeyHex; ethaddress: EthAddress}, + {params: {pubkey: string}; body: {ethaddress: string}}, + EmptyResponseData, + EmptyMeta >; - deleteFeeRecipient( - pubkey: string - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void}, - HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND - > + deleteFeeRecipient: Endpoint< + // ⏎ + "DELETE", + {pubkey: PubkeyHex}, + {params: {pubkey: string}}, + EmptyResponseData, + EmptyMeta >; - listGraffiti(pubkey: string): Promise>; - setGraffiti( - pubkey: string, - graffiti: string - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void}, - HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND - > + getGraffiti: Endpoint< + // ⏎ + "GET", + {pubkey: PubkeyHex}, + {params: {pubkey: string}}, + GraffitiData, + EmptyMeta >; - deleteGraffiti( - pubkey: string - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void}, - HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND - > + setGraffiti: Endpoint< + "POST", + {pubkey: PubkeyHex; graffiti: Graffiti}, + {params: {pubkey: string}; body: {graffiti: string}}, + EmptyResponseData, + EmptyMeta + >; + deleteGraffiti: Endpoint< + // ⏎ + "DELETE", + {pubkey: PubkeyHex}, + {params: {pubkey: string}}, + EmptyResponseData, + EmptyMeta >; - getGasLimit(pubkey: string): Promise>; - setGasLimit( - pubkey: string, - gasLimit: number - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void}, - HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND - > + getGasLimit: Endpoint< + // ⏎ + "GET", + {pubkey: PubkeyHex}, + {params: {pubkey: string}}, + GasLimitData, + EmptyMeta + >; + setGasLimit: Endpoint< + "POST", + {pubkey: PubkeyHex; gasLimit: number}, + {params: {pubkey: string}; body: {gas_limit: string}}, + EmptyResponseData, + EmptyMeta >; - deleteGasLimit( - pubkey: string - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void}, - HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND - > + deleteGasLimit: Endpoint< + // ⏎ + "DELETE", + {pubkey: PubkeyHex}, + {params: {pubkey: string}}, + EmptyResponseData, + EmptyMeta >; - getBuilderBoostFactor( - pubkey: string - ): Promise>; - setBuilderBoostFactor( - pubkey: string, - builderBoostFactor: bigint - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void}, - HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND - > + getBuilderBoostFactor: Endpoint< + "GET", + {pubkey: PubkeyHex}, + {params: {pubkey: string}}, + BuilderBoostFactorData, + EmptyMeta + >; + setBuilderBoostFactor: Endpoint< + "POST", + {pubkey: PubkeyHex; builderBoostFactor: bigint}, + {params: {pubkey: string}; body: {builder_boost_factor: string}}, + EmptyResponseData, + EmptyMeta >; - deleteBuilderBoostFactor( - pubkey: string - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: void; [HttpStatusCode.NO_CONTENT]: void}, - HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND - > + deleteBuilderBoostFactor: Endpoint< + "DELETE", + {pubkey: PubkeyHex}, + {params: {pubkey: string}}, + EmptyResponseData, + EmptyMeta >; /** @@ -277,257 +362,299 @@ export type Api = { * client. This endpoint returns a `SignedVoluntaryExit` object, which can be used to initiate voluntary exit via the * beacon node's [submitPoolVoluntaryExit](https://ethereum.github.io/beacon-APIs/#/Beacon/submitPoolVoluntaryExit) endpoint. * - * @param pubkey Public key of an active validator known to the validator client - * @param epoch Minimum epoch for processing exit. Defaults to the current epoch if not set - * @returns Signed voluntary exit message + * Returns the signed voluntary exit message * * https://github.com/ethereum/keymanager-APIs/blob/7105e749e11dd78032ea275cc09bf62ecd548fca/keymanager-oapi.yaml */ - signVoluntaryExit( - pubkey: PubkeyHex, - epoch?: Epoch - ): Promise< - ApiClientResponse< - {[HttpStatusCode.OK]: {data: phase0.SignedVoluntaryExit}}, - HttpStatusCode.UNAUTHORIZED | HttpStatusCode.FORBIDDEN | HttpStatusCode.NOT_FOUND - > + signVoluntaryExit: Endpoint< + "POST", + { + /** Public key of an active validator known to the validator client */ + pubkey: PubkeyHex; + /** Minimum epoch for processing exit. Defaults to the current epoch if not set */ + epoch?: Epoch; + }, + {params: {pubkey: string}; query: {epoch?: number}}, + phase0.SignedVoluntaryExit, + EmptyMeta >; }; -export const routesData: RoutesData = { - listKeys: {url: "/eth/v1/keystores", method: "GET"}, - importKeystores: {url: "/eth/v1/keystores", method: "POST"}, - deleteKeys: {url: "/eth/v1/keystores", method: "DELETE"}, - - listRemoteKeys: {url: "/eth/v1/remotekeys", method: "GET"}, - importRemoteKeys: {url: "/eth/v1/remotekeys", method: "POST"}, - deleteRemoteKeys: {url: "/eth/v1/remotekeys", method: "DELETE"}, - - listFeeRecipient: {url: "/eth/v1/validator/{pubkey}/feerecipient", method: "GET"}, - setFeeRecipient: {url: "/eth/v1/validator/{pubkey}/feerecipient", method: "POST", statusOk: 202}, - deleteFeeRecipient: {url: "/eth/v1/validator/{pubkey}/feerecipient", method: "DELETE", statusOk: 204}, - - listGraffiti: {url: "/eth/v1/validator/{pubkey}/graffiti", method: "GET"}, - setGraffiti: {url: "/eth/v1/validator/{pubkey}/graffiti", method: "POST", statusOk: 202}, - deleteGraffiti: {url: "/eth/v1/validator/{pubkey}/graffiti", method: "DELETE", statusOk: 204}, - - getGasLimit: {url: "/eth/v1/validator/{pubkey}/gas_limit", method: "GET"}, - setGasLimit: {url: "/eth/v1/validator/{pubkey}/gas_limit", method: "POST", statusOk: 202}, - deleteGasLimit: {url: "/eth/v1/validator/{pubkey}/gas_limit", method: "DELETE", statusOk: 204}, - - getBuilderBoostFactor: {url: "/eth/v1/validator/{pubkey}/builder_boost_factor", method: "GET"}, - setBuilderBoostFactor: {url: "/eth/v1/validator/{pubkey}/builder_boost_factor", method: "POST", statusOk: 202}, - deleteBuilderBoostFactor: {url: "/eth/v1/validator/{pubkey}/builder_boost_factor", method: "DELETE", statusOk: 204}, - - signVoluntaryExit: {url: "/eth/v1/validator/{pubkey}/voluntary_exit", method: "POST"}, -}; - -/* eslint-disable @typescript-eslint/naming-convention */ - -export type ReqTypes = { - listKeys: ReqEmpty; - importKeystores: { - body: { - keystores: KeystoreStr[]; - passwords: string[]; - slashing_protection?: SlashingProtectionData; - }; - }; - deleteKeys: {body: {pubkeys: string[]}}; - - listRemoteKeys: ReqEmpty; - importRemoteKeys: { - body: { - remote_keys: Pick[]; - }; - }; - deleteRemoteKeys: {body: {pubkeys: string[]}}; - - listFeeRecipient: {params: {pubkey: string}}; - setFeeRecipient: {params: {pubkey: string}; body: {ethaddress: string}}; - deleteFeeRecipient: {params: {pubkey: string}}; - - listGraffiti: {params: {pubkey: string}}; - setGraffiti: {params: {pubkey: string}; body: {graffiti: string}}; - deleteGraffiti: {params: {pubkey: string}}; - - getGasLimit: {params: {pubkey: string}}; - setGasLimit: {params: {pubkey: string}; body: {gas_limit: string}}; - deleteGasLimit: {params: {pubkey: string}}; - - getBuilderBoostFactor: {params: {pubkey: string}}; - setBuilderBoostFactor: {params: {pubkey: string}; body: {builder_boost_factor: string}}; - deleteBuilderBoostFactor: {params: {pubkey: string}}; - - signVoluntaryExit: {params: {pubkey: string}; query: {epoch?: number}}; -}; - -export function getReqSerializers(): ReqSerializers { +export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { - listKeys: reqEmpty, + listKeys: { + url: "/eth/v1/keystores", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + }, importKeystores: { - writeReq: (keystores, passwords, slashing_protection) => ({body: {keystores, passwords, slashing_protection}}), - parseReq: ({body: {keystores, passwords, slashing_protection}}) => [keystores, passwords, slashing_protection], - schema: {body: Schema.Object}, + url: "/eth/v1/keystores", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({keystores, passwords, slashingProtection}) => ({ + body: {keystores, passwords, slashing_protection: slashingProtection}, + }), + parseReqJson: ({body: {keystores, passwords, slashing_protection}}) => ({ + keystores, + passwords, + slashingProtection: slashing_protection, + }), + schema: {body: Schema.Object}, + }), + resp: JsonOnlyResponseCodec, }, deleteKeys: { - writeReq: (pubkeys) => ({body: {pubkeys}}), - parseReq: ({body: {pubkeys}}) => [pubkeys], - schema: {body: Schema.Object}, + url: "/eth/v1/keystores", + method: "DELETE", + req: JsonOnlyReq({ + writeReqJson: ({pubkeys}) => ({body: {pubkeys}}), + parseReqJson: ({body: {pubkeys}}) => ({pubkeys}), + schema: {body: Schema.Object}, + }), + resp: { + onlySupport: WireFormat.json, + data: JsonOnlyResponseCodec.data, + meta: EmptyMetaCodec, + transform: { + toResponse: (data) => { + const {statuses, slashing_protection} = data as { + statuses: ResponseStatus[]; + slashing_protection: SlashingProtectionData; + }; + return {data: statuses, slashing_protection}; + }, + fromResponse: (resp) => { + const {data, slashing_protection} = resp as { + data: ResponseStatus[]; + slashing_protection: SlashingProtectionData; + }; + return {data: {statuses: data, slashingProtection: slashing_protection}}; + }, + }, + }, }, - listRemoteKeys: reqEmpty, + listRemoteKeys: { + url: "/eth/v1/remotekeys", + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + }, importRemoteKeys: { - writeReq: (remote_keys) => ({body: {remote_keys}}), - parseReq: ({body: {remote_keys}}) => [remote_keys], - schema: {body: Schema.Object}, + url: "/eth/v1/remotekeys", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({remoteSigners}) => ({body: {remote_keys: remoteSigners}}), + parseReqJson: ({body: {remote_keys}}) => ({remoteSigners: remote_keys}), + schema: {body: Schema.Object}, + }), + resp: JsonOnlyResponseCodec, }, deleteRemoteKeys: { - writeReq: (pubkeys) => ({body: {pubkeys}}), - parseReq: ({body: {pubkeys}}) => [pubkeys], - schema: {body: Schema.Object}, + url: "/eth/v1/remotekeys", + method: "DELETE", + req: JsonOnlyReq({ + writeReqJson: ({pubkeys}) => ({body: {pubkeys}}), + parseReqJson: ({body: {pubkeys}}) => ({pubkeys}), + schema: {body: Schema.Object}, + }), + resp: JsonOnlyResponseCodec, }, listFeeRecipient: { - writeReq: (pubkey) => ({params: {pubkey}}), - parseReq: ({params: {pubkey}}) => [pubkey], - schema: { - params: {pubkey: Schema.StringRequired}, + url: "/eth/v1/validator/{pubkey}/feerecipient", + method: "GET", + req: { + writeReq: ({pubkey}) => ({params: {pubkey}}), + parseReq: ({params: {pubkey}}) => ({pubkey}), + schema: { + params: {pubkey: Schema.StringRequired}, + }, + }, + resp: { + onlySupport: WireFormat.json, + data: FeeRecipientDataType, + meta: EmptyMetaCodec, }, }, setFeeRecipient: { - writeReq: (pubkey, ethaddress) => ({params: {pubkey}, body: {ethaddress}}), - parseReq: ({params: {pubkey}, body: {ethaddress}}) => [pubkey, ethaddress], - schema: { - params: {pubkey: Schema.StringRequired}, - body: Schema.Object, - }, + url: "/eth/v1/validator/{pubkey}/feerecipient", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({pubkey, ethaddress}) => ({params: {pubkey}, body: {ethaddress}}), + parseReqJson: ({params: {pubkey}, body: {ethaddress}}) => ({pubkey, ethaddress}), + schema: { + params: {pubkey: Schema.StringRequired}, + body: Schema.Object, + }, + }), + resp: EmptyResponseCodec, }, deleteFeeRecipient: { - writeReq: (pubkey) => ({params: {pubkey}}), - parseReq: ({params: {pubkey}}) => [pubkey], - schema: { - params: {pubkey: Schema.StringRequired}, + url: "/eth/v1/validator/{pubkey}/feerecipient", + method: "DELETE", + req: { + writeReq: ({pubkey}) => ({params: {pubkey}}), + parseReq: ({params: {pubkey}}) => ({pubkey}), + schema: { + params: {pubkey: Schema.StringRequired}, + }, }, + resp: EmptyResponseCodec, }, - listGraffiti: { - writeReq: (pubkey) => ({params: {pubkey}}), - parseReq: ({params: {pubkey}}) => [pubkey], - schema: { - params: {pubkey: Schema.StringRequired}, + getGraffiti: { + url: "/eth/v1/validator/{pubkey}/graffiti", + method: "GET", + req: { + writeReq: ({pubkey}) => ({params: {pubkey}}), + parseReq: ({params: {pubkey}}) => ({pubkey}), + schema: { + params: {pubkey: Schema.StringRequired}, + }, + }, + resp: { + onlySupport: WireFormat.json, + data: GraffitiDataType, + meta: EmptyMetaCodec, }, }, setGraffiti: { - writeReq: (pubkey, graffiti) => ({params: {pubkey}, body: {graffiti}}), - parseReq: ({params: {pubkey}, body: {graffiti}}) => [pubkey, graffiti], - schema: { - params: {pubkey: Schema.StringRequired}, - body: Schema.Object, - }, + url: "/eth/v1/validator/{pubkey}/graffiti", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({pubkey, graffiti}) => ({params: {pubkey}, body: {graffiti}}), + parseReqJson: ({params: {pubkey}, body: {graffiti}}) => ({pubkey, graffiti}), + schema: { + params: {pubkey: Schema.StringRequired}, + body: Schema.Object, + }, + }), + resp: EmptyResponseCodec, }, deleteGraffiti: { - writeReq: (pubkey) => ({params: {pubkey}}), - parseReq: ({params: {pubkey}}) => [pubkey], - schema: { - params: {pubkey: Schema.StringRequired}, + url: "/eth/v1/validator/{pubkey}/graffiti", + method: "DELETE", + req: { + writeReq: ({pubkey}) => ({params: {pubkey}}), + parseReq: ({params: {pubkey}}) => ({pubkey}), + schema: { + params: {pubkey: Schema.StringRequired}, + }, }, + resp: EmptyResponseCodec, }, getGasLimit: { - writeReq: (pubkey) => ({params: {pubkey}}), - parseReq: ({params: {pubkey}}) => [pubkey], - schema: { - params: {pubkey: Schema.StringRequired}, + url: "/eth/v1/validator/{pubkey}/gas_limit", + method: "GET", + req: { + writeReq: ({pubkey}) => ({params: {pubkey}}), + parseReq: ({params: {pubkey}}) => ({pubkey}), + schema: { + params: {pubkey: Schema.StringRequired}, + }, + }, + resp: { + onlySupport: WireFormat.json, + data: GasLimitDataType, + meta: EmptyMetaCodec, }, }, setGasLimit: { - writeReq: (pubkey, gasLimit) => ({params: {pubkey}, body: {gas_limit: gasLimit.toString(10)}}), - parseReq: ({params: {pubkey}, body: {gas_limit}}) => [pubkey, parseGasLimit(gas_limit)], - schema: { - params: {pubkey: Schema.StringRequired}, - body: Schema.Object, - }, + url: "/eth/v1/validator/{pubkey}/gas_limit", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({pubkey, gasLimit}) => ({params: {pubkey}, body: {gas_limit: gasLimit.toString(10)}}), + parseReqJson: ({params: {pubkey}, body: {gas_limit}}) => ({pubkey, gasLimit: parseGasLimit(gas_limit)}), + schema: { + params: {pubkey: Schema.StringRequired}, + body: Schema.Object, + }, + }), + resp: EmptyResponseCodec, }, deleteGasLimit: { - writeReq: (pubkey) => ({params: {pubkey}}), - parseReq: ({params: {pubkey}}) => [pubkey], - schema: { - params: {pubkey: Schema.StringRequired}, + url: "/eth/v1/validator/{pubkey}/gas_limit", + method: "DELETE", + req: { + writeReq: ({pubkey}) => ({params: {pubkey}}), + parseReq: ({params: {pubkey}}) => ({pubkey}), + schema: { + params: {pubkey: Schema.StringRequired}, + }, }, + resp: EmptyResponseCodec, }, getBuilderBoostFactor: { - writeReq: (pubkey) => ({params: {pubkey}}), - parseReq: ({params: {pubkey}}) => [pubkey], - schema: { - params: {pubkey: Schema.StringRequired}, + url: "/eth/v1/validator/{pubkey}/builder_boost_factor", + method: "GET", + req: { + writeReq: ({pubkey}) => ({params: {pubkey}}), + parseReq: ({params: {pubkey}}) => ({pubkey}), + schema: { + params: {pubkey: Schema.StringRequired}, + }, + }, + resp: { + onlySupport: WireFormat.json, + data: BuilderBoostFactorDataType, + meta: EmptyMetaCodec, }, }, setBuilderBoostFactor: { - writeReq: (pubkey, builderBoostFactor) => ({ - params: {pubkey}, - body: {builder_boost_factor: builderBoostFactor.toString(10)}, + url: "/eth/v1/validator/{pubkey}/builder_boost_factor", + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({pubkey, builderBoostFactor}) => ({ + params: {pubkey}, + body: {builder_boost_factor: builderBoostFactor.toString(10)}, + }), + parseReqJson: ({params: {pubkey}, body: {builder_boost_factor}}) => ({ + pubkey, + builderBoostFactor: BigInt(builder_boost_factor), + }), + schema: { + params: {pubkey: Schema.StringRequired}, + body: Schema.Object, + }, }), - parseReq: ({params: {pubkey}, body: {builder_boost_factor}}) => [pubkey, BigInt(builder_boost_factor)], - schema: { - params: {pubkey: Schema.StringRequired}, - body: Schema.Object, - }, + resp: EmptyResponseCodec, }, deleteBuilderBoostFactor: { - writeReq: (pubkey) => ({params: {pubkey}}), - parseReq: ({params: {pubkey}}) => [pubkey], - schema: { - params: {pubkey: Schema.StringRequired}, + url: "/eth/v1/validator/{pubkey}/builder_boost_factor", + method: "DELETE", + req: { + writeReq: ({pubkey}) => ({params: {pubkey}}), + parseReq: ({params: {pubkey}}) => ({pubkey}), + schema: { + params: {pubkey: Schema.StringRequired}, + }, }, + resp: EmptyResponseCodec, }, signVoluntaryExit: { - writeReq: (pubkey, epoch) => ({params: {pubkey}, query: epoch !== undefined ? {epoch} : {}}), - parseReq: ({params: {pubkey}, query: {epoch}}) => [pubkey, epoch], - schema: { - params: {pubkey: Schema.StringRequired}, - query: {epoch: Schema.Uint}, + url: "/eth/v1/validator/{pubkey}/voluntary_exit", + method: "POST", + req: { + writeReq: ({pubkey, epoch}) => ({params: {pubkey}, query: {epoch}}), + parseReq: ({params: {pubkey}, query: {epoch}}) => ({pubkey, epoch}), + schema: { + params: {pubkey: Schema.StringRequired}, + query: {epoch: Schema.Uint}, + }, + }, + resp: { + data: ssz.phase0.SignedVoluntaryExit, + meta: EmptyMetaCodec, }, }, }; } -export function getReturnTypes(): ReturnTypes { - return { - listKeys: jsonType("snake"), - importKeystores: jsonType("snake"), - deleteKeys: jsonType("snake"), - - listRemoteKeys: jsonType("snake"), - importRemoteKeys: jsonType("snake"), - deleteRemoteKeys: jsonType("snake"), - - listFeeRecipient: jsonType("snake"), - listGraffiti: jsonType("snake"), - getGasLimit: ContainerData( - new ContainerType( - { - pubkey: stringType, - gasLimit: ssz.UintNum64, - }, - {jsonCase: "eth2"} - ) - ), - getBuilderBoostFactor: ContainerData( - new ContainerType( - { - pubkey: stringType, - builderBoostFactor: ssz.UintBn64, - }, - {jsonCase: "eth2"} - ) - ), - signVoluntaryExit: ContainerData(ssz.phase0.SignedVoluntaryExit), - }; -} - function parseGasLimit(gasLimitInput: string | number): number { if ((typeof gasLimitInput !== "string" && typeof gasLimitInput !== "number") || `${gasLimitInput}`.trim() === "") { throw Error("Not valid Gas Limit"); diff --git a/packages/api/src/keymanager/server/index.ts b/packages/api/src/keymanager/server/index.ts index 0b51be896258..f4d0e75f971e 100644 --- a/packages/api/src/keymanager/server/index.ts +++ b/packages/api/src/keymanager/server/index.ts @@ -1,26 +1,19 @@ +import type {FastifyInstance} from "fastify"; import {ChainForkConfig} from "@lodestar/config"; -import {ServerApi} from "../../interfaces.js"; -import { - ServerInstance, - ServerRoutes, - getGenericJsonServer, - registerRoute, - type RouteConfig, -} from "../../utils/server/index.js"; -import {Api, ReqTypes, routesData, getReturnTypes, getReqSerializers} from "../routes.js"; +import {ApplicationMethods, FastifyRoute, FastifyRoutes, createFastifyRoutes} from "../../utils/server/index.js"; +import {Endpoints, getDefinitions} from "../routes.js"; +import {AnyEndpoint} from "../../utils/codecs.js"; -// Re-export for convenience -export type {RouteConfig}; +export type KeymanagerApiMethods = ApplicationMethods; -export function getRoutes(config: ChainForkConfig, api: ServerApi): ServerRoutes { - // All routes return JSON, use a server auto-generator - return getGenericJsonServer, ReqTypes>({routesData, getReturnTypes, getReqSerializers}, config, api); +export function getRoutes(config: ChainForkConfig, methods: KeymanagerApiMethods): FastifyRoutes { + return createFastifyRoutes(getDefinitions(config), methods); } -export function registerRoutes(server: ServerInstance, config: ChainForkConfig, api: ServerApi): void { - const routes = getRoutes(config, api); +export function registerRoutes(server: FastifyInstance, config: ChainForkConfig, methods: KeymanagerApiMethods): void { + const routes = getRoutes(config, methods); for (const route of Object.values(routes)) { - registerRoute(server, route); + server.route(route as FastifyRoute); } } diff --git a/packages/api/src/server/index.ts b/packages/api/src/server/index.ts new file mode 100644 index 000000000000..ca5151d86a16 --- /dev/null +++ b/packages/api/src/server/index.ts @@ -0,0 +1,2 @@ +// Server specific code to be exported from /server subpath +export * from "../utils/server/index.js"; diff --git a/packages/api/src/utils/acceptHeader.ts b/packages/api/src/utils/acceptHeader.ts deleted file mode 100644 index dfe858cd3cba..000000000000 --- a/packages/api/src/utils/acceptHeader.ts +++ /dev/null @@ -1,81 +0,0 @@ -import {ResponseFormat} from "../interfaces.js"; - -enum MediaType { - json = "application/json", - ssz = "application/octet-stream", -} - -const MEDIA_TYPES: { - [K in ResponseFormat]: MediaType; -} = { - json: MediaType.json, - ssz: MediaType.ssz, -}; - -function responseFormatFromMediaType(mediaType: MediaType): ResponseFormat { - switch (mediaType) { - default: - case MediaType.json: - return "json"; - case MediaType.ssz: - return "ssz"; - } -} - -export function writeAcceptHeader(format?: ResponseFormat): MediaType { - return format === undefined ? MEDIA_TYPES["json"] : MEDIA_TYPES[format]; -} - -export function parseAcceptHeader(accept?: string): ResponseFormat { - // Use json by default. - if (!accept) { - return "json"; - } - - const mediaTypes = Object.values(MediaType); - - // Respect Quality Values per RFC-9110 - // Acceptable mime-types are comma separated with optional whitespace - return responseFormatFromMediaType( - accept - .toLowerCase() - .split(",") - .map((x) => x.trim()) - .reduce( - (best: [number, MediaType], current: string): [number, MediaType] => { - // An optional `;` delimiter is used to separate the mime-type from the weight - // Normalize here, using 1 as the default qvalue - const quality = current.includes(";") ? current.split(";") : [current, "q=1"]; - - const mediaType = quality[0].trim() as MediaType; - - // If the mime type isn't acceptable, move on to the next entry - if (!mediaTypes.includes(mediaType)) { - return best; - } - - // Otherwise, the portion after the semicolon has optional whitespace and the constant prefix "q=" - const weight = quality[1].trim(); - if (!weight.startsWith("q=")) { - // If the format is invalid simply move on to the next entry - return best; - } - - const qvalue = +weight.replace("q=", ""); - if (isNaN(qvalue) || qvalue > 1 || qvalue <= 0) { - // If we can't convert the qvalue to a valid number, move on - return best; - } - - if (qvalue < best[0]) { - // This mime type is not preferred - return best; - } - - // This mime type is preferred - return [qvalue, mediaType]; - }, - [0, MediaType.json] - )[1] - ); -} diff --git a/packages/api/src/utils/client/client.ts b/packages/api/src/utils/client/client.ts deleted file mode 100644 index 59a06aa024a2..000000000000 --- a/packages/api/src/utils/client/client.ts +++ /dev/null @@ -1,125 +0,0 @@ -import {TimeoutError, mapValues} from "@lodestar/utils"; -import {compileRouteUrlFormater} from "../urlFormat.js"; -import {RouteDef, ReqGeneric, ReturnTypes, TypeJson, ReqSerializer, ReqSerializers, RoutesData} from "../types.js"; -import {APIClientHandler} from "../../interfaces.js"; -import {FetchOpts, HttpError, IHttpClient} from "./httpClient.js"; -import {HttpStatusCode} from "./httpStatusCode.js"; - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -type ExtraOpts = {retries?: number}; -type ParametersWithOptionalExtraOpts any> = [...Parameters, ExtraOpts] | Parameters; - -export type ApiWithExtraOpts> = { - [K in keyof T]: (...args: ParametersWithOptionalExtraOpts) => ReturnType; -}; - -// See /packages/api/src/routes/index.ts for reasoning - -/** - * Format FetchFn opts from Fn arguments given a route definition and request serializer. - * For routes that return only JSOn use @see getGenericJsonClient - */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function getFetchOptsSerializer any, ReqType extends ReqGeneric>( - routeDef: RouteDef, - reqSerializer: ReqSerializer, - routeId: string -) { - const urlFormater = compileRouteUrlFormater(routeDef.url); - - return function getFetchOpts(...args: Parameters): FetchOpts { - const req = reqSerializer.writeReq(...args); - return { - url: urlFormater(req.params ?? {}), - method: routeDef.method, - query: Object.keys(req.query ?? {}).length ? req.query : undefined, - body: req.body as unknown, - headers: req.headers, - routeId, - }; - }; -} - -/** - * Generate `getFetchOptsSerializer()` functions for all routes in `Api` - */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function getFetchOptsSerializers< - Api extends Record, - ReqTypes extends {[K in keyof Api]: ReqGeneric}, ->(routesData: RoutesData, reqSerializers: ReqSerializers) { - return mapValues(routesData, (routeDef, routeId) => - getFetchOptsSerializer(routeDef, reqSerializers[routeId], routeId as string) - ); -} - -/** - * Get a generic JSON client from route definition, request serializer and return types. - */ -export function generateGenericJsonClient< - Api extends Record, - ReqTypes extends {[K in keyof Api]: ReqGeneric}, ->( - routesData: RoutesData, - reqSerializers: ReqSerializers, - returnTypes: ReturnTypes, - fetchFn: IHttpClient -): ApiWithExtraOpts { - return mapValues(routesData, (routeDef, routeId) => { - const fetchOptsSerializer = getFetchOptsSerializer(routeDef, reqSerializers[routeId], routeId as string); - const returnType = returnTypes[routeId as keyof ReturnTypes] as TypeJson | null; - - return async function request( - ...args: ParametersWithOptionalExtraOpts - ): Promise> { - try { - // extract the extraOpts if provided - // - const argLen = (args as any[])?.length ?? 0; - const lastArg = (args as any[])[argLen] as ExtraOpts | undefined; - const retries = lastArg?.retries; - const extraOpts = {retries}; - - if (returnType) { - // open extraOpts first if some serializer wants to add some overriding param - const res = await fetchFn.json({ - ...extraOpts, - ...fetchOptsSerializer(...(args as Parameters)), - }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/return-await - return {ok: true, response: returnType.fromJson(res.body), status: res.status} as ReturnType; - } else { - // We need to avoid parsing the response as the servers might just - // response status 200 and close the request instead of writing an - // empty json response. We return the status code. - const res = await fetchFn.request({ - ...extraOpts, - ...fetchOptsSerializer(...(args as Parameters)), - }); - - // eslint-disable-next-line @typescript-eslint/return-await - return {ok: true, response: undefined, status: res.status} as ReturnType; - } - } catch (err) { - if (err instanceof HttpError) { - return { - ok: false, - status: err.status, - error: {code: err.status, message: err.message, operationId: routeId}, - } as ReturnType; - } - - if (err instanceof TimeoutError) { - return { - ok: false, - status: HttpStatusCode.INTERNAL_SERVER_ERROR, - error: {code: HttpStatusCode.INTERNAL_SERVER_ERROR, message: err.message, operationId: routeId}, - } as ReturnType; - } - - throw err; - } - }; - }) as unknown as ApiWithExtraOpts; -} diff --git a/packages/api/src/utils/client/error.ts b/packages/api/src/utils/client/error.ts new file mode 100644 index 000000000000..b5e5da52aaea --- /dev/null +++ b/packages/api/src/utils/client/error.ts @@ -0,0 +1,10 @@ +export class ApiError extends Error { + status: number; + operationId: string; + + constructor(message: string, status: number, operationId: string) { + super(`${operationId} failed with status ${status}: ${message}`); + this.status = status; + this.operationId = operationId; + } +} diff --git a/packages/api/src/utils/client/httpClient.ts b/packages/api/src/utils/client/httpClient.ts index 9cbadde1cca5..eeccd59d2032 100644 --- a/packages/api/src/utils/client/httpClient.ts +++ b/packages/api/src/utils/client/httpClient.ts @@ -1,14 +1,37 @@ -import {ErrorAborted, Logger, TimeoutError, isValidHttpUrl, toBase64, retry} from "@lodestar/utils"; -import {ReqGeneric, RouteDef} from "../index.js"; -import {ApiClientResponse, ApiClientSuccessResponse} from "../../interfaces.js"; +import {ErrorAborted, Logger, MapDef, TimeoutError, isValidHttpUrl, retry} from "@lodestar/utils"; +import {mergeHeaders} from "../headers.js"; +import {Endpoint} from "../types.js"; +import {WireFormat} from "../wireFormat.js"; +import {HttpStatusCode} from "../httpStatusCode.js"; +import { + ApiRequestInit, + ApiRequestInitRequired, + ExtraRequestInit, + RouteDefinitionExtra, + UrlInit, + UrlInitRequired, + createApiRequest, +} from "./request.js"; +import {ApiResponse} from "./response.js"; +import {Metrics} from "./metrics.js"; import {fetch, isFetchError} from "./fetch.js"; -import {stringifyQuery, urlJoin} from "./format.js"; -import type {Metrics} from "./metrics.js"; -import {HttpStatusCode} from "./httpStatusCode.js"; -/** A higher default timeout, validator will sets its own shorter timeoutMs */ +/** A higher default timeout, validator will set its own shorter timeoutMs */ const DEFAULT_TIMEOUT_MS = 60_000; -const DEFAULT_ROUTE_ID = "unknown"; +const DEFAULT_RETRIES = 0; +const DEFAULT_RETRY_DELAY = 200; +/** + * Default to JSON to ensure compatibility with other clients, can be overridden + * per route in case spec states that SSZ requests must be supported by server. + * Alternatively, can be configured via CLI flag to use SSZ for all routes. + */ +const DEFAULT_REQUEST_WIRE_FORMAT = WireFormat.json; +/** + * For responses, it is possible to default to SSZ without breaking compatibility with + * other clients as we will just be stating a preference to receive a SSZ response from + * the server but will still accept a JSON response in case the server does not support it. + */ +const DEFAULT_RESPONSE_WIRE_FORMAT = WireFormat.ssz; const URL_SCORE_DELTA_SUCCESS = 1; /** Require 2 success to recover from 1 failed request */ @@ -17,75 +40,26 @@ const URL_SCORE_DELTA_ERROR = 2 * URL_SCORE_DELTA_SUCCESS; const URL_SCORE_MAX = 10 * URL_SCORE_DELTA_SUCCESS; const URL_SCORE_MIN = 0; -export class HttpError extends Error { - status: number; - url: string; - - constructor(message: string, status: number, url: string) { - super(message); - this.status = status; - this.url = url; - } -} - -export class ApiError extends Error { - status: number; - operationId: string; - - constructor(message: string, status: number, operationId: string) { - super(message); - this.status = status; - this.operationId = operationId; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static assert(res: ApiClientResponse, message?: string): asserts res is ApiClientSuccessResponse { - if (!res.ok) { - throw new ApiError( - [message, res.error.message].filter(Boolean).join(" - "), - res.error.code, - res.error.operationId - ); - } - } - - toString(): string { - return `${this.message} (status=${this.status}, operationId=${this.operationId})`; - } -} - -export interface URLOpts { - baseUrl: string; - timeoutMs?: number; - bearerToken?: string; - extraHeaders?: Record; -} - -export type FetchOpts = { - url: RouteDef["url"]; - method: RouteDef["method"]; - query?: ReqGeneric["query"]; - body?: ReqGeneric["body"]; - headers?: ReqGeneric["headers"]; - /** Optional, for metrics */ - routeId?: string; - timeoutMs?: number; - retries?: number; +export const defaultInit: Required = { + timeoutMs: DEFAULT_TIMEOUT_MS, + retries: DEFAULT_RETRIES, + retryDelay: DEFAULT_RETRY_DELAY, + requestWireFormat: DEFAULT_REQUEST_WIRE_FORMAT, + responseWireFormat: DEFAULT_RESPONSE_WIRE_FORMAT, }; export interface IHttpClient { - baseUrl: string; - json(opts: FetchOpts): Promise<{status: HttpStatusCode; body: T}>; - request(opts: FetchOpts): Promise<{status: HttpStatusCode; body: void}>; - arrayBuffer(opts: FetchOpts): Promise<{status: HttpStatusCode; body: ArrayBuffer}>; + readonly baseUrl: string; + + request( + definition: RouteDefinitionExtra, + args: E["args"], + localInit?: ApiRequestInit + ): Promise>; } -export type HttpClientOptions = ({baseUrl: string} | {urls: (string | URLOpts)[]}) & { - timeoutMs?: number; - bearerToken?: string; - extraHeaders?: Record; - /** Return an AbortSignal to be attached to all requests */ - getAbortSignal?: () => AbortSignal | undefined; +export type HttpClientOptions = ({baseUrl: string} | {urls: (string | UrlInit)[]}) & { + globalInit?: ApiRequestInit; /** Override fetch function */ fetch?: typeof fetch; }; @@ -95,65 +69,63 @@ export type HttpClientModules = { metrics?: Metrics; }; -export type {Metrics}; - export class HttpClient implements IHttpClient { - private readonly globalTimeoutMs: number; - private readonly globalBearerToken: string | null; - private readonly globalExtraHeaders: Record | null; - private readonly getAbortSignal?: () => AbortSignal | undefined; + readonly urlsInits: UrlInitRequired[] = []; + + private readonly signal: null | AbortSignal; private readonly fetch: typeof fetch; private readonly metrics: null | Metrics; private readonly logger: null | Logger; - private readonly urlsOpts: URLOpts[] = []; private readonly urlsScore: number[]; + /** + * Cache to keep track of routes per server that do not support SSZ. This cache will only be + * populated if we receive a 415 error response from the server after sending a SSZ request body. + * The request will be retried using a JSON body and all subsequent requests will only use JSON. + */ + private readonly sszNotSupportedByRouteIdByUrlIndex = new MapDef>(() => new Map()); + get baseUrl(): string { - return this.urlsOpts[0].baseUrl; + return this.urlsInits[0].baseUrl; } - /** - * timeoutMs = config.params.SECONDS_PER_SLOT * 1000 - */ constructor(opts: HttpClientOptions, {logger, metrics}: HttpClientModules = {}) { // Cast to all types optional since they are defined with syntax `HttpClientOptions = A | B` - const {baseUrl, urls = []} = opts as {baseUrl?: string; urls?: (string | URLOpts)[]}; - - // Append to Partial object to not fill urlOpts with properties with value undefined - const allUrlOpts: Partial = {}; - if (opts.bearerToken) allUrlOpts.bearerToken = opts.bearerToken; - if (opts.timeoutMs !== undefined) allUrlOpts.timeoutMs = opts.timeoutMs; - if (opts.extraHeaders) allUrlOpts.extraHeaders = opts.extraHeaders; + const {baseUrl, urls = []} = opts as {baseUrl?: string; urls?: (string | UrlInit)[]}; + // Do not merge global signal into url inits + const {signal, ...globalInit} = opts.globalInit ?? {}; // opts.baseUrl is equivalent to `urls: [{baseUrl}]` // unshift opts.baseUrl to urls, without mutating opts.urls - for (const [i, urlOrOpts] of [...(baseUrl ? [baseUrl] : []), ...urls].entries()) { - const urlOpts: URLOpts = typeof urlOrOpts === "string" ? {baseUrl: urlOrOpts, ...allUrlOpts} : urlOrOpts; - - if (!urlOpts.baseUrl) { - throw Error(`HttpClient.urls[${i}] is empty or undefined: ${urlOpts.baseUrl}`); + for (const [i, urlOrInit] of [...(baseUrl ? [baseUrl] : []), ...urls].entries()) { + const init = typeof urlOrInit === "string" ? {baseUrl: urlOrInit} : urlOrInit; + const urlInit: UrlInit = { + ...globalInit, + ...init, + headers: mergeHeaders(globalInit.headers, init.headers), + }; + + if (!urlInit.baseUrl) { + throw Error(`HttpClient.urls[${i}] is empty or undefined: ${urlInit.baseUrl}`); } - if (!isValidHttpUrl(urlOpts.baseUrl)) { - throw Error(`HttpClient.urls[${i}] must be a valid URL: ${urlOpts.baseUrl}`); + if (!isValidHttpUrl(urlInit.baseUrl)) { + throw Error(`HttpClient.urls[${i}] must be a valid URL: ${urlInit.baseUrl}`); } // De-duplicate by baseUrl, having two baseUrls with different token or timeouts does not make sense - if (!this.urlsOpts.some((opt) => opt.baseUrl === urlOpts.baseUrl)) { - this.urlsOpts.push(urlOpts); + if (!this.urlsInits.some((opt) => opt.baseUrl === urlInit.baseUrl)) { + this.urlsInits.push({...urlInit, urlIndex: i} as UrlInitRequired); } } - if (this.urlsOpts.length === 0) { + if (this.urlsInits.length === 0) { throw Error("Must set at least 1 URL in HttpClient opts"); } // Initialize scores to max value to only query first URL on start - this.urlsScore = this.urlsOpts.map(() => URL_SCORE_MAX); + this.urlsScore = this.urlsInits.map(() => URL_SCORE_MAX); - this.globalTimeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS; - this.globalBearerToken = opts.bearerToken ?? null; - this.globalExtraHeaders = opts.extraHeaders ?? null; - this.getAbortSignal = opts.getAbortSignal; + this.signal = signal ?? null; this.fetch = opts.fetch ?? fetch; this.metrics = metrics ?? null; this.logger = logger ?? null; @@ -161,58 +133,38 @@ 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.urlsOpts[i].baseUrl}, this.urlsScore[i]); + metrics.urlsScore.set({urlIndex: i, baseUrl: this.urlsInits[i].baseUrl}, this.urlsScore[i]); } }); } } - async json(opts: FetchOpts): Promise<{status: HttpStatusCode; body: T}> { - return this.requestWithBodyWithRetries(opts, (res) => res.json() as Promise); - } - - async request(opts: FetchOpts): Promise<{status: HttpStatusCode; body: void}> { - return this.requestWithBodyWithRetries(opts, async () => undefined); - } + async request( + definition: RouteDefinitionExtra, + args: E["args"], + localInit: ApiRequestInit = {} + ): Promise> { + if (this.urlsInits.length === 1) { + const init = mergeInits(definition, this.urlsInits[0], localInit); - async arrayBuffer(opts: FetchOpts): Promise<{status: HttpStatusCode; body: ArrayBuffer}> { - return this.requestWithBodyWithRetries(opts, (res) => res.arrayBuffer()); - } - - private async requestWithBodyWithRetries( - opts: FetchOpts, - getBody: (res: Response) => Promise - ): Promise<{status: HttpStatusCode; body: T}> { - if (opts.retries !== undefined) { - const routeId = opts.routeId ?? DEFAULT_ROUTE_ID; - - return retry( - async (_attempt) => { - return this.requestWithBodyWithFallbacks(opts, getBody); - }, - { - retries: opts.retries, - retryDelay: 200, - signal: this.getAbortSignal?.(), - onRetry: (e, attempt) => { - this.logger?.debug("Retrying request", {routeId, attempt, lastError: e.message}); - }, - } - ); + if (init.retries > 0) { + return this.requestWithRetries(definition, args, init); + } else { + return this.requestFallbackToJson(definition, args, init); + } } else { - return this.requestWithBodyWithFallbacks(opts, getBody); + return this.requestWithFallbacks(definition, args, localInit); } } - private async requestWithBodyWithFallbacks( - opts: FetchOpts, - getBody: (res: Response) => Promise - ): Promise<{status: HttpStatusCode; body: T}> { - // Early return when no fallback URLs are setup - if (this.urlsOpts.length === 1) { - return this.requestWithBody(this.urlsOpts[0], opts, getBody); - } - + /** + * Send request to primary server first, retry failed requests on fallbacks + */ + private async requestWithFallbacks( + definition: RouteDefinitionExtra, + args: E["args"], + localInit: ApiRequestInit + ): Promise> { let i = 0; // Goals: @@ -221,9 +173,9 @@ export class HttpClient implements IHttpClient { // - until first server is shown to be reliable again, contact all servers // First loop: retry in sequence, query next URL only after previous errors - for (; i < this.urlsOpts.length; i++) { + for (; i < this.urlsInits.length; i++) { try { - return await new Promise<{status: HttpStatusCode; body: T}>((resolve, reject) => { + const res = await new Promise>((resolve, reject) => { let requestCount = 0; let errorCount = 0; @@ -231,10 +183,9 @@ export class HttpClient implements IHttpClient { // Score each URL available: // - 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.urlsOpts.length; i++) { - const urlOpts = this.urlsOpts[i]; - const {baseUrl} = urlOpts; - const routeId = opts.routeId ?? DEFAULT_ROUTE_ID; + for (; i < this.urlsInits.length; i++) { + const baseUrl = this.urlsInits[i].baseUrl; + const routeId = definition.operationId; if (i > 0) { this.metrics?.requestToFallbacks.inc({routeId, baseUrl}); @@ -242,13 +193,32 @@ export class HttpClient implements IHttpClient { } // eslint-disable-next-line @typescript-eslint/naming-convention - const i_ = i; // Keep local copy of i variable to index urlScore after requestWithBody() resolves + const i_ = i; // Keep local copy of i variable to index urlScore after requestMethod() resolves + + const urlInit = this.urlsInits[i]; + if (urlInit === undefined) { + throw Error(`Url at index ${i} does not exist`); + } + const init = mergeInits(definition, urlInit, localInit); - this.requestWithBody(urlOpts, opts, getBody).then( - (res) => { - this.urlsScore[i_] = Math.min(URL_SCORE_MAX, this.urlsScore[i_] + URL_SCORE_DELTA_SUCCESS); - // Resolve immediately on success - resolve(res); + const requestMethod = (init.retries > 0 ? this.requestWithRetries : this.requestFallbackToJson).bind(this); + + requestMethod(definition, args, init).then( + async (res) => { + if (res.ok) { + this.urlsScore[i_] = Math.min(URL_SCORE_MAX, this.urlsScore[i_] + URL_SCORE_DELTA_SUCCESS); + // Resolve immediately on success + resolve(res); + } else { + this.urlsScore[i_] = Math.max(URL_SCORE_MIN, this.urlsScore[i_] - URL_SCORE_DELTA_ERROR); + + // Resolve failed response only when all queried URLs have errored + if (++errorCount >= requestCount) { + resolve(res); + } else { + this.logger?.debug("Request error, retrying", {routeId, baseUrl}, res.error() as Error); + } + } }, (err) => { this.urlsScore[i_] = Math.max(URL_SCORE_MIN, this.urlsScore[i_] - URL_SCORE_DELTA_ERROR); @@ -271,8 +241,17 @@ export class HttpClient implements IHttpClient { } } }); + if (res.ok) { + return res; + } else { + if (i >= this.urlsInits.length - 1) { + return res; + } else { + this.logger?.debug("Request error, retrying", {}, res.error() as Error); + } + } } catch (e) { - if (i >= this.urlsOpts.length - 1) { + if (i >= this.urlsInits.length - 1) { throw e; } else { this.logger?.debug("Request error, retrying", {}, e as Error); @@ -283,75 +262,125 @@ export class HttpClient implements IHttpClient { throw Error("loop ended without return or rejection"); } - private async requestWithBody( - urlOpts: URLOpts, - opts: FetchOpts, - getBody: (res: Response) => Promise - ): Promise<{status: HttpStatusCode; body: T}> { - const baseUrl = urlOpts.baseUrl; - const bearerToken = urlOpts.bearerToken ?? this.globalBearerToken; - const extraHeaders = urlOpts.extraHeaders ?? this.globalExtraHeaders; - const timeoutMs = opts.timeoutMs ?? urlOpts.timeoutMs ?? this.globalTimeoutMs; + /** + * Send request to single URL, retry failed requests on same server + */ + private async requestWithRetries( + definition: RouteDefinitionExtra, + args: E["args"], + init: ApiRequestInitRequired + ): Promise> { + const {retries, retryDelay, signal} = init; + const routeId = definition.operationId; + + return retry( + async (attempt) => { + const res = await this.requestFallbackToJson(definition, args, init); + if (!res.ok && attempt <= retries) { + throw res.error(); + } + return res; + }, + { + retries, + retryDelay, + // Local signal takes precedence over global signal + signal: signal ?? this.signal ?? undefined, + onRetry: (e, attempt) => { + this.logger?.debug("Retrying request", {routeId, attempt, lastError: e.message}); + }, + } + ); + } - // Implement fetch timeout - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? timeoutMs ?? this.globalTimeoutMs); + /** + * Send request to single URL, SSZ requests will be retried using JSON + * if a 415 error response is returned by the server. All subsequent requests + * to this server for the route will always be sent as JSON afterwards. + */ + private async requestFallbackToJson( + definition: RouteDefinitionExtra, + args: E["args"], + init: ApiRequestInitRequired + ): Promise> { + const {urlIndex} = init; + const routeId = definition.operationId; + + const sszNotSupportedByRouteId = this.sszNotSupportedByRouteIdByUrlIndex.getOrDefault(urlIndex); + if (sszNotSupportedByRouteId.has(routeId)) { + init.requestWireFormat = WireFormat.json; + } - // Attach global signal to this request's controller - const onGlobalSignalAbort = (): void => controller.abort(); - const signalGlobal = this.getAbortSignal?.(); - signalGlobal?.addEventListener("abort", onGlobalSignalAbort); + const res = await this._request(definition, args, init); - const routeId = opts.routeId ?? DEFAULT_ROUTE_ID; - const timer = this.metrics?.requestTime.startTimer({routeId}); + if (res.status === HttpStatusCode.UNSUPPORTED_MEDIA_TYPE && init.requestWireFormat === WireFormat.ssz) { + this.logger?.debug("SSZ request failed with status 415, retrying using JSON", {routeId, urlIndex}); - try { - const url = new URL(urlJoin(baseUrl, opts.url) + (opts.query ? "?" + stringifyQuery(opts.query) : "")); + sszNotSupportedByRouteId.set(routeId, true); + init.requestWireFormat = WireFormat.json; - const headers = - extraHeaders && opts.headers ? {...extraHeaders, ...opts.headers} : opts.headers || extraHeaders || {}; - if (opts.body && headers["Content-Type"] === undefined) { - headers["Content-Type"] = "application/json"; - } - if (bearerToken && headers["Authorization"] === undefined) { - headers["Authorization"] = `Bearer ${bearerToken}`; - } - if (url.username || url.password) { - if (headers["Authorization"] === undefined) { - headers["Authorization"] = `Basic ${toBase64(decodeURIComponent(`${url.username}:${url.password}`))}`; - } - // Remove the username and password from the URL - url.username = ""; - url.password = ""; - } + return this._request(definition, args, init); + } - this.logger?.debug("HttpClient request", {routeId}); + return res; + } - const res = await this.fetch(url, { - method: opts.method, - headers: headers as Record, - body: opts.body ? JSON.stringify(opts.body) : undefined, - signal: controller.signal, - }); + /** + * Send request to single URL + */ + private async _request( + definition: RouteDefinitionExtra, + args: E["args"], + init: ApiRequestInitRequired + ): Promise> { + const abortSignals = [this.signal, init.signal]; - if (!res.ok) { - const errBody = await res.text(); - throw new HttpError(`${res.statusText}: ${getErrorMessage(errBody)}`, res.status, url.toString()); + // Implement fetch timeout + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), init.timeoutMs); + init.signal = controller.signal; + + // Attach global/local signal to this request's controller + const onSignalAbort = (): void => controller.abort(); + abortSignals.forEach((s) => s?.addEventListener("abort", onSignalAbort)); + + const routeId = definition.operationId; + const {baseUrl, requestWireFormat, responseWireFormat} = init; + const timer = this.metrics?.requestTime.startTimer({routeId}); + + try { + this.logger?.debug("API request", {routeId, requestWireFormat, responseWireFormat}); + const request = createApiRequest(definition, args, init); + const response = await this.fetch(request.url, request); + const apiResponse = new ApiResponse(definition, response.body, response); + + if (!apiResponse.ok) { + await apiResponse.errorBody(); + this.logger?.debug("API response error", {routeId, status: apiResponse.status}); + this.metrics?.requestErrors.inc({routeId, baseUrl}); + return apiResponse; } const streamTimer = this.metrics?.streamTime.startTimer({routeId}); - const body = await getBody(res); - streamTimer?.(); - this.logger?.debug("HttpClient response", {routeId}); - return {status: res.status, body}; + try { + await apiResponse.rawBody(); + this.logger?.debug("API response success", { + routeId, + status: apiResponse.status, + wireFormat: apiResponse.wireFormat(), + }); + return apiResponse; + } finally { + streamTimer?.(); + } } catch (e) { this.metrics?.requestErrors.inc({routeId, baseUrl}); - if (isAbortedError(e as Error)) { - if (signalGlobal?.aborted) { - throw new ErrorAborted("REST client"); + if (isAbortedError(e)) { + if (abortSignals.some((s) => s?.aborted)) { + throw new ErrorAborted(`${routeId} request`); } else if (controller.signal.aborted) { - throw new TimeoutError("request"); + throw new TimeoutError(`${routeId} request`); } else { throw Error("Unknown aborted error"); } @@ -362,24 +391,32 @@ export class HttpClient implements IHttpClient { timer?.(); clearTimeout(timeout); - signalGlobal?.removeEventListener("abort", onGlobalSignalAbort); + abortSignals.forEach((s) => s?.removeEventListener("abort", onSignalAbort)); } } } -function isAbortedError(e: Error): boolean { - return isFetchError(e) && e.type === "aborted"; +function mergeInits( + definition: RouteDefinitionExtra, + urlInit: UrlInitRequired, + localInit: ApiRequestInit +): ApiRequestInitRequired { + return { + ...defaultInit, + ...definition.init, + // Sanitize user provided values + ...removeUndefined(urlInit), + ...removeUndefined(localInit), + headers: mergeHeaders(urlInit.headers, localInit.headers), + }; } -function getErrorMessage(errBody: string): string { - try { - const errJson = JSON.parse(errBody) as {message: string}; - if (errJson.message) { - return errJson.message; - } else { - return errBody; - } - } catch (e) { - return errBody; - } +function removeUndefined(obj: T): {[K in keyof T]: Exclude} { + return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v !== undefined)) as { + [K in keyof T]: Exclude; + }; +} + +function isAbortedError(e: unknown): boolean { + return isFetchError(e) && e.type === "aborted"; } diff --git a/packages/api/src/utils/client/index.ts b/packages/api/src/utils/client/index.ts index 7198c22ab89b..f07a7bad5a64 100644 --- a/packages/api/src/utils/client/index.ts +++ b/packages/api/src/utils/client/index.ts @@ -1,3 +1,7 @@ -export * from "./client.js"; -export * from "./httpClient.js"; export * from "./fetch.js"; +export * from "./httpClient.js"; +export * from "./method.js"; +export * from "./metrics.js"; +export * from "./request.js"; +export * from "./response.js"; +export * from "./error.js"; diff --git a/packages/api/src/utils/client/method.ts b/packages/api/src/utils/client/method.ts new file mode 100644 index 000000000000..6d450f75201b --- /dev/null +++ b/packages/api/src/utils/client/method.ts @@ -0,0 +1,50 @@ +import {mapValues} from "@lodestar/utils"; +import {Endpoint, HasOnlyOptionalProps, RouteDefinition, RouteDefinitions} from "../types.js"; +import {compileRouteUrlFormatter} from "../urlFormat.js"; +import {IHttpClient} from "./httpClient.js"; +import {ApiRequestInit} from "./request.js"; +import {ApiResponse} from "./response.js"; + +export type ApiClientMethod = E["args"] extends void + ? (init?: ApiRequestInit) => Promise> + : HasOnlyOptionalProps extends true + ? (args?: E["args"], init?: ApiRequestInit) => Promise> + : (args: E["args"], init?: ApiRequestInit) => Promise>; + +export type ApiClientMethods> = {[K in keyof Es]: ApiClientMethod}; + +export function createApiClientMethod( + definition: RouteDefinition, + client: IHttpClient, + operationId: string +): ApiClientMethod { + const urlFormatter = compileRouteUrlFormatter(definition.url); + const definitionExtended = { + ...definition, + urlFormatter, + operationId, + }; + + // If the request args is void, then completely remove the args parameter + if ( + definition.req.schema.body === undefined && + definition.req.schema.params === undefined && + definition.req.schema.query === undefined + ) { + return (async (init?: ApiRequestInit) => { + return client.request(definitionExtended, undefined, init); + }) as ApiClientMethod; + } + return async (args?: E["args"], init?: ApiRequestInit) => { + return client.request(definitionExtended, args ?? {}, init); + }; +} + +export function createApiClientMethods>( + definitions: RouteDefinitions, + client: IHttpClient +): ApiClientMethods { + return mapValues(definitions, (definition, operationId) => { + return createApiClientMethod(definition, client, operationId as string); + }) as unknown as ApiClientMethods; +} diff --git a/packages/api/src/utils/client/request.ts b/packages/api/src/utils/client/request.ts new file mode 100644 index 000000000000..f8b41ca4d706 --- /dev/null +++ b/packages/api/src/utils/client/request.ts @@ -0,0 +1,108 @@ +import {HttpHeader, MediaType, mergeHeaders, setAuthorizationHeader} from "../headers.js"; +import { + Endpoint, + JsonRequestMethods, + RequestWithBodyCodec, + RouteDefinition, + SszRequestMethods, + isRequestWithoutBody, +} from "../types.js"; +import {WireFormat} from "../wireFormat.js"; +import {stringifyQuery, urlJoin} from "./format.js"; + +export type ExtraRequestInit = { + /** Wire format to use in HTTP requests to server */ + requestWireFormat?: `${WireFormat}`; + /** Preferred wire format for HTTP responses from server */ + responseWireFormat?: `${WireFormat}`; + /** Timeout of requests in milliseconds */ + timeoutMs?: number; + /** Number of retries per request */ + retries?: number; + /** Retry delay, only relevant if retries > 0 */ + retryDelay?: number; +}; + +export type OptionalRequestInit = { + bearerToken?: string; +}; + +export type UrlInit = ApiRequestInit & {baseUrl?: string}; +export type UrlInitRequired = ApiRequestInit & {urlIndex: number; baseUrl: string}; +export type ApiRequestInit = ExtraRequestInit & OptionalRequestInit & RequestInit; +export type ApiRequestInitRequired = Required & UrlInitRequired; + +/** Route definition with computed extra properties */ +export type RouteDefinitionExtra = RouteDefinition & { + operationId: string; + urlFormatter: (args: Record) => string; +}; + +export function createApiRequest( + definition: RouteDefinitionExtra, + args: E["args"], + init: ApiRequestInitRequired +): Request { + const headers = new Headers(init.headers); + + let req: E["request"]; + + if (isRequestWithoutBody(definition)) { + req = definition.req.writeReq(args); + } else { + const requestWireFormat = (definition.req as RequestWithBodyCodec).onlySupport ?? init.requestWireFormat; + switch (requestWireFormat) { + case WireFormat.json: + req = (definition.req as JsonRequestMethods).writeReqJson(args); + if (req.body) { + req.body = JSON.stringify(req.body); + headers.set(HttpHeader.ContentType, MediaType.json); + } + break; + case WireFormat.ssz: + req = (definition.req as SszRequestMethods).writeReqSsz(args); + if (req.body) { + headers.set(HttpHeader.ContentType, MediaType.ssz); + } + break; + default: + throw Error(`Invalid requestWireFormat: ${requestWireFormat}`); + } + } + const queryString = req.query ? stringifyQuery(req.query) : ""; + const url = new URL( + urlJoin(init.baseUrl, definition.urlFormatter(req.params ?? {})) + (queryString ? `?${queryString}` : "") + ); + setAuthorizationHeader(url, headers, init); + + if (definition.resp.isEmpty) { + // Do not set Accept header + } else if (definition.resp.onlySupport !== undefined) { + switch (definition.resp.onlySupport) { + case WireFormat.json: + headers.set(HttpHeader.Accept, MediaType.json); + break; + case WireFormat.ssz: + headers.set(HttpHeader.Accept, MediaType.ssz); + break; + } + } else { + switch (init.responseWireFormat) { + case WireFormat.json: + headers.set(HttpHeader.Accept, `${MediaType.json};q=1,${MediaType.ssz};q=0.9`); + break; + case WireFormat.ssz: + headers.set(HttpHeader.Accept, `${MediaType.ssz};q=1,${MediaType.json};q=0.9`); + break; + default: + throw Error(`Invalid responseWireFormat: ${init.responseWireFormat}`); + } + } + + return new Request(url, { + ...init, + method: definition.method, + headers: mergeHeaders(headers, req.headers), + body: req.body as BodyInit, + }); +} diff --git a/packages/api/src/utils/client/response.ts b/packages/api/src/utils/client/response.ts new file mode 100644 index 000000000000..b7039f4865ae --- /dev/null +++ b/packages/api/src/utils/client/response.ts @@ -0,0 +1,201 @@ +import {HeadersExtra, HttpHeader, parseContentTypeHeader} from "../headers.js"; +import {HttpStatusCode} from "../httpStatusCode.js"; +import {Endpoint} from "../types.js"; +import {WireFormat, getWireFormat} from "../wireFormat.js"; +import {ApiError} from "./error.js"; +import {RouteDefinitionExtra} from "./request.js"; + +export type RawBody = + | {type: WireFormat.json; value: unknown} + | {type: WireFormat.ssz; value: Uint8Array} + | {type?: never; value?: never}; + +export class ApiResponse extends Response { + private definition: RouteDefinitionExtra; + private _wireFormat?: WireFormat | null; + private _rawBody?: RawBody; + private _errorBody?: string; + private _meta?: E["meta"]; + private _value?: E["return"]; + + constructor(definition: RouteDefinitionExtra, body?: BodyInit | null, init?: ResponseInit) { + super(body, init); + this.definition = definition; + } + + wireFormat(): WireFormat | null { + if (this._wireFormat === undefined) { + if (this.definition.resp.isEmpty) { + return (this._wireFormat = null); + } + + const contentType = this.headers.get(HttpHeader.ContentType); + if (contentType === null) { + if (this.status === HttpStatusCode.NO_CONTENT) { + return (this._wireFormat = null); + } else { + throw Error("Content-Type header is required in response"); + } + } + + const mediaType = parseContentTypeHeader(contentType); + if (mediaType === null) { + throw Error(`Unsupported response media type: ${contentType.split(";", 1)[0]}`); + } + + const wireFormat = getWireFormat(mediaType); + + const {onlySupport} = this.definition.resp; + if (onlySupport !== undefined && wireFormat !== onlySupport) { + throw Error(`Method only supports ${onlySupport.toUpperCase()} responses`); + } + + this._wireFormat = wireFormat; + } + return this._wireFormat; + } + + async rawBody(): Promise { + this.assertOk(); + + if (!this._rawBody) { + switch (this.wireFormat()) { + case WireFormat.json: + this._rawBody = { + type: WireFormat.json, + value: await super.json(), + }; + break; + case WireFormat.ssz: + this._rawBody = { + type: WireFormat.ssz, + value: new Uint8Array(await this.arrayBuffer()), + }; + break; + default: + this._rawBody = {}; + } + } + return this._rawBody; + } + + meta(): E["meta"] { + this.assertOk(); + + if (!this._meta) { + switch (this.wireFormat()) { + case WireFormat.json: { + const rawBody = this.resolvedRawBody(); + const metaJson = this.definition.resp.transform + ? this.definition.resp.transform.fromResponse(rawBody.value).meta + : rawBody.value; + this._meta = this.definition.resp.meta.fromJson(metaJson); + break; + } + case WireFormat.ssz: + this._meta = this.definition.resp.meta.fromHeaders(new HeadersExtra(this.headers)); + break; + } + } + return this._meta; + } + + value(): E["return"] { + this.assertOk(); + + if (!this._value) { + const rawBody = this.resolvedRawBody(); + const meta = this.meta(); + switch (rawBody.type) { + case WireFormat.json: { + const dataJson = this.definition.resp.transform + ? this.definition.resp.transform.fromResponse(rawBody.value).data + : (rawBody.value as Record)?.data; + this._value = this.definition.resp.data.fromJson(dataJson, meta); + break; + } + case WireFormat.ssz: + this._value = this.definition.resp.data.deserialize(rawBody.value, meta); + break; + } + } + return this._value; + } + + ssz(): Uint8Array { + this.assertOk(); + + const rawBody = this.resolvedRawBody(); + switch (rawBody.type) { + case WireFormat.json: + return this.definition.resp.data.serialize(this.value(), this.meta()); + case WireFormat.ssz: + return rawBody.value; + default: + return new Uint8Array(); + } + } + + json(): Awaited> { + this.assertOk(); + + const rawBody = this.resolvedRawBody(); + switch (rawBody.type) { + case WireFormat.json: + return rawBody.value; + case WireFormat.ssz: + return this.definition.resp.data.toJson(this.value(), this.meta()); + default: + return {}; + } + } + + assertOk(): void { + if (!this.ok) { + throw this.error(); + } + } + + error(): ApiError | null { + if (this.ok) { + return null; + } + + return new ApiError(getErrorMessage(this.resolvedErrorBody()), this.status, this.definition.operationId); + } + + async errorBody(): Promise { + if (!this._errorBody) { + this._errorBody = await this.text(); + } + return this._errorBody; + } + + private resolvedRawBody(): RawBody { + if (!this._rawBody) { + throw Error("rawBody() must be called first"); + } + return this._rawBody; + } + + private resolvedErrorBody(): string { + if (!this._errorBody) { + throw Error("errorBody() must be called first"); + } + + return this._errorBody; + } +} + +function getErrorMessage(errBody: string): string { + try { + const errJson = JSON.parse(errBody) as {message?: string}; + if (errJson.message) { + return errJson.message; + } else { + return errBody; + } + } catch (e) { + return errBody; + } +} diff --git a/packages/api/src/utils/codecs.ts b/packages/api/src/utils/codecs.ts new file mode 100644 index 000000000000..36d905583098 --- /dev/null +++ b/packages/api/src/utils/codecs.ts @@ -0,0 +1,144 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {ArrayType, ListBasicType, ListCompositeType, Type, isBasicType, isCompositeType} from "@chainsafe/ssz"; +import {ForkName} from "@lodestar/params"; +import {objectToExpectedCase} from "@lodestar/utils"; +import { + RequestWithoutBodyCodec, + RequestWithBodyCodec, + ResponseCodec, + ResponseDataCodec, + ResponseMetadataCodec, + Endpoint, + SszRequestMethods, +} from "./types.js"; +import {WireFormat} from "./wireFormat.js"; + +// Utility types / codecs + +export type EmptyArgs = void; +export type EmptyRequest = Record; +export type EmptyResponseData = void; +export type EmptyMeta = void; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AnyEndpoint = Endpoint; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type EmptyRequestEndpoint = Endpoint; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type EmptyResponseEndpoint = Endpoint; + +/** Shortcut for routes that have no params, query */ +export const EmptyRequestCodec: RequestWithoutBodyCodec = { + writeReq: () => ({}), + parseReq: () => {}, + schema: {}, +}; + +export function JsonOnlyReq( + req: Omit, keyof SszRequestMethods> +): RequestWithBodyCodec { + return { + ...req, + writeReqSsz: () => { + throw Error("Not implemented"); + }, + parseReqSsz: () => { + throw Error("Not implemented"); + }, + onlySupport: WireFormat.json, + }; +} + +export const EmptyResponseDataCodec: ResponseDataCodec = { + toJson: () => {}, + fromJson: () => {}, + serialize: () => new Uint8Array(), + deserialize: () => {}, +}; + +export const EmptyMetaCodec: ResponseMetadataCodec = { + toJson: () => {}, + fromJson: () => {}, + toHeadersObject: () => ({}), + fromHeaders: () => {}, +}; + +export const EmptyResponseCodec: ResponseCodec = { + data: EmptyResponseDataCodec, + meta: EmptyMetaCodec, + isEmpty: true, +}; + +export function ArrayOf(elementType: Type, limit = Infinity): ArrayType, unknown, unknown> { + if (isCompositeType(elementType)) { + return new ListCompositeType(elementType, limit) as unknown as ArrayType, unknown, unknown>; + } else if (isBasicType(elementType)) { + return new ListBasicType(elementType, limit) as unknown as ArrayType, unknown, unknown>; + } else { + throw Error(`Unknown type ${elementType.typeName}`); + } +} + +export function WithMeta(getType: (m: M) => Type): ResponseDataCodec { + return { + toJson: (data, meta: M) => getType(meta).toJson(data), + fromJson: (data, meta: M) => getType(meta).fromJson(data), + serialize: (data, meta: M) => getType(meta).serialize(data), + deserialize: (data, meta: M) => getType(meta).deserialize(data), + }; +} + +export function WithVersion( + getType: (v: ForkName) => Type +): ResponseDataCodec { + return { + toJson: (data, meta: M) => getType(meta.version).toJson(data), + fromJson: (data, meta: M) => getType(meta.version).fromJson(data), + serialize: (data, meta: M) => getType(meta.version).serialize(data), + deserialize: (data, meta: M) => getType(meta.version).deserialize(data), + }; +} + +export function JsonOnlyResp( + resp: Omit, "data"> & { + data: Omit["data"], "serialize" | "deserialize">; + } +): ResponseCodec { + return { + ...resp, + data: { + ...resp.data, + serialize: () => { + throw Error("Not implemented"); + }, + deserialize: () => { + throw Error("Not implemented"); + }, + }, + onlySupport: WireFormat.json, + }; +} + +export const JsonOnlyResponseCodec: ResponseCodec = { + data: { + toJson: (data: Record) => { + // JSON fields use snake case across all existing routes + return objectToExpectedCase(data, "snake"); + }, + fromJson: (data) => { + if (typeof data !== "object" || data === null) { + throw Error("JSON must be of type object"); + } + // All JSON inside the JS code must be camel case + return objectToExpectedCase(data as Record, "camel"); + }, + serialize: () => { + throw Error("Not implemented"); + }, + deserialize: () => { + throw Error("Not implemented"); + }, + }, + meta: EmptyMetaCodec, + onlySupport: WireFormat.json, +}; diff --git a/packages/api/src/utils/fork.ts b/packages/api/src/utils/fork.ts new file mode 100644 index 000000000000..290147e7e47a --- /dev/null +++ b/packages/api/src/utils/fork.ts @@ -0,0 +1,40 @@ +import {ForkName, isForkBlobs, isForkExecution, isForkLightClient} from "@lodestar/params"; +import {allForks, ssz} from "@lodestar/types"; + +export function toForkName(version: string): ForkName { + // Teku returns fork as UPPERCASE + version = version.toLowerCase(); + + // Un-safe external data, validate version is known ForkName value + if (!(version in ForkName)) throw Error(`Invalid version ${version}`); + + return version as ForkName; +} + +export function getLightClientForkTypes(fork: ForkName): allForks.AllForksLightClientSSZTypes { + if (!isForkLightClient(fork)) { + throw Error(`Invalid fork=${fork} for lightclient fork types`); + } + return ssz.allForksLightClient[fork]; +} + +export function getExecutionForkTypes(fork: ForkName): allForks.AllForksExecutionSSZTypes { + if (!isForkExecution(fork)) { + throw Error(`Invalid fork=${fork} for execution fork types`); + } + return ssz.allForksExecution[fork]; +} + +export function getBlindedForkTypes(fork: ForkName): allForks.AllForksBlindedSSZTypes { + if (!isForkExecution(fork)) { + throw Error(`Invalid fork=${fork} for blinded fork types`); + } + return ssz.allForksBlinded[fork] as allForks.AllForksBlindedSSZTypes; +} + +export function getBlobsForkTypes(fork: ForkName): allForks.AllForksBlobsSSZTypes { + if (!isForkBlobs(fork)) { + throw Error(`Invalid fork=${fork} for blobs fork types`); + } + return ssz.allForksBlobs[fork]; +} diff --git a/packages/api/src/utils/headers.ts b/packages/api/src/utils/headers.ts new file mode 100644 index 000000000000..5e39e5765958 --- /dev/null +++ b/packages/api/src/utils/headers.ts @@ -0,0 +1,161 @@ +import {toBase64} from "@lodestar/utils"; + +export enum HttpHeader { + ContentType = "content-type", + Accept = "accept", + Authorization = "authorization", +} + +export enum MediaType { + json = "application/json", + ssz = "application/octet-stream", +} + +export const SUPPORTED_MEDIA_TYPES = Object.values(MediaType); + +function isSupportedMediaType(mediaType: string | null, supported: MediaType[]): mediaType is MediaType { + return mediaType !== null && supported.includes(mediaType as MediaType); +} + +export function parseContentTypeHeader(contentType?: string): MediaType | null { + if (!contentType) { + return null; + } + + const mediaType = contentType.split(";", 1)[0].trim().toLowerCase(); + + return isSupportedMediaType(mediaType, SUPPORTED_MEDIA_TYPES) ? mediaType : null; +} + +export function parseAcceptHeader(accept?: string, supported = SUPPORTED_MEDIA_TYPES): MediaType | null { + if (!accept) { + return null; + } + + // Respect Quality Values per RFC-9110 + // Acceptable mime-types are comma separated with optional whitespace + return accept + .toLowerCase() + .split(",") + .map((x) => x.trim()) + .reduce( + (best: [number, MediaType | null], current: string): [number, MediaType | null] => { + // An optional `;` delimiter is used to separate the mime-type from the weight + // Normalize here, using 1 as the default qvalue + const quality = current.includes(";") ? current.split(";") : [current, "q=1"]; + + const mediaType = quality[0].trim(); + + // If the mime type isn't acceptable, move on to the next entry + if (!isSupportedMediaType(mediaType, supported)) { + return best; + } + + // Otherwise, the portion after the semicolon has optional whitespace and the constant prefix "q=" + const weight = quality[1].trim(); + if (!weight.startsWith("q=")) { + // If the format is invalid simply move on to the next entry + return best; + } + + const qvalue = +weight.replace("q=", ""); + if (isNaN(qvalue) || qvalue > 1 || qvalue <= 0) { + // If we can't convert the qvalue to a valid number, move on + return best; + } + + if (qvalue < best[0]) { + // This mime type is not preferred + return best; + } + + // This mime type is preferred + return [qvalue, mediaType]; + }, + [0, null] + )[1]; +} + +export function setAuthorizationHeader(url: URL, headers: Headers, {bearerToken}: {bearerToken?: string}): void { + if (bearerToken && !headers.has(HttpHeader.Authorization)) { + headers.set(HttpHeader.Authorization, `Bearer ${bearerToken}`); + } + if (url.username || url.password) { + if (!headers.has(HttpHeader.Authorization)) { + headers.set(HttpHeader.Authorization, `Basic ${toBase64(decodeURIComponent(`${url.username}:${url.password}`))}`); + } + // Remove the username and password from the URL + url.username = ""; + url.password = ""; + } +} + +export function mergeHeaders(a: HeadersInit | undefined, b: HeadersInit | undefined): Headers { + if (!a) { + return new Headers(b); + } + const headers = new Headers(a); + if (!b) { + return headers; + } + if (Array.isArray(b)) { + for (const [key, value] of b) { + headers.set(key, value); + } + } else if (b instanceof Headers) { + for (const [key, value] of b as unknown as Iterable<[string, string]>) { + headers.set(key, value); + } + } else { + for (const [key, value] of Object.entries(b)) { + headers.set(key, value); + } + } + return headers; +} + +/** + * Get header from request headers, by default an error will be thrown if the header + * is not present. The header can be marked as optional in which case the return value + * might be `undefined` but no error will be thrown if header is missing. + */ +export function fromHeaders, R extends boolean = true>( + headers: T, + name: Extract, + required: R = true as R +): R extends true ? string : string | undefined { + // Fastify converts all headers to lower case + const header = headers[name.toLowerCase()]; + + if (header === undefined && required) { + throw Error(`${name} header is required`); + } + + return header as R extends true ? string : string | undefined; +} + +/** + * Extension of Headers object returned by Fetch API + */ +export class HeadersExtra extends Headers { + /** + * Get required header from response headers + */ + getRequired(name: string): string { + const header = this.get(name); + + if (header === null) { + throw Error(`${name} header is required in response`); + } + + return header; + } + + /** + * Get optional header from response headers. + * Return default value if it does not exist + */ + getOrDefault(name: string, defaultValue: string): string { + return this.get(name) ?? defaultValue; + } +} diff --git a/packages/api/src/utils/client/httpStatusCode.ts b/packages/api/src/utils/httpStatusCode.ts similarity index 100% rename from packages/api/src/utils/client/httpStatusCode.ts rename to packages/api/src/utils/httpStatusCode.ts diff --git a/packages/api/src/utils/index.ts b/packages/api/src/utils/index.ts index e87a2272d637..73d46e497c03 100644 --- a/packages/api/src/utils/index.ts +++ b/packages/api/src/utils/index.ts @@ -1,3 +1,3 @@ export * from "./schema.js"; export * from "./types.js"; -export {compileRouteUrlFormater, toColonNotationPath} from "./urlFormat.js"; +export {compileRouteUrlFormatter, toColonNotationPath} from "./urlFormat.js"; diff --git a/packages/api/src/utils/metadata.ts b/packages/api/src/utils/metadata.ts new file mode 100644 index 000000000000..8376113efec8 --- /dev/null +++ b/packages/api/src/utils/metadata.ts @@ -0,0 +1,164 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import {ContainerType, ValueOf} from "@chainsafe/ssz"; +import {ForkName} from "@lodestar/params"; +import {StringType, ssz, stringType} from "@lodestar/types"; +import {ResponseMetadataCodec} from "./types.js"; +import {toBoolean} from "./serdes.js"; +import {toForkName} from "./fork.js"; + +export const VersionType = new ContainerType({ + /** + * Fork code name + */ + version: new StringType(), +}); +VersionType.fields.version.fromJson = (json) => { + return toForkName(stringType.fromJson(json)); +}; + +export const ExecutionOptimisticType = new ContainerType( + { + /** + * True if the response references an unverified execution payload. + * Optimistic information may be invalidated at a later time. + */ + executionOptimistic: ssz.Boolean, + }, + {jsonCase: "eth2"} +); + +export const ExecutionOptimisticAndFinalizedType = new ContainerType( + { + ...ExecutionOptimisticType.fields, + /** + * True if the response references the finalized history of the chain, as determined by fork choice + */ + finalized: ssz.Boolean, + }, + {jsonCase: "eth2"} +); + +export const ExecutionOptimisticAndVersionType = new ContainerType( + { + ...ExecutionOptimisticType.fields, + ...VersionType.fields, + }, + {jsonCase: "eth2"} +); + +export const ExecutionOptimisticFinalizedAndVersionType = new ContainerType( + { + ...ExecutionOptimisticAndFinalizedType.fields, + ...VersionType.fields, + }, + {jsonCase: "eth2"} +); + +export const ExecutionOptimisticAndDependentRootType = new ContainerType( + { + ...ExecutionOptimisticType.fields, + /** + * The block root that this response is dependent on + */ + dependentRoot: stringType, + }, + {jsonCase: "eth2"} +); + +export type VersionMeta = ValueOf; +export type ExecutionOptimisticMeta = ValueOf; +export type ExecutionOptimisticAndFinalizedMeta = ValueOf; +export type ExecutionOptimisticAndVersionMeta = ValueOf; +export type ExecutionOptimisticFinalizedAndVersionMeta = ValueOf; +export type ExecutionOptimisticAndDependentRootMeta = ValueOf; + +export enum MetaHeader { + Version = "Eth-Consensus-Version", + ConsensusBlockValue = "Eth-Consensus-Block-Value", + ExecutionPayloadBlinded = "Eth-Execution-Payload-Blinded", + ExecutionPayloadValue = "Eth-Execution-Payload-Value", + + /* Lodestar-specific (non-standardized) headers */ + Finalized = "Eth-Consensus-Finalized", + DependentRoot = "Eth-Consensus-Dependent-Root", + ExecutionOptimistic = "Eth-Execution-Optimistic", + ExecutionPayloadSource = "Eth-Execution-Payload-Source", +} + +export const ExecutionOptimisticCodec: ResponseMetadataCodec = { + toJson: (val) => ExecutionOptimisticType.toJson(val), + fromJson: (val) => ExecutionOptimisticType.fromJson(val), + toHeadersObject: (val) => ({ + [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), + }), + fromHeaders: (headers) => ({ + executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), + }), +}; + +export const VersionCodec: ResponseMetadataCodec = { + toJson: (val) => VersionType.toJson(val), + fromJson: (val) => VersionType.fromJson(val), + toHeadersObject: (val) => ({ + [MetaHeader.Version]: val.version, + }), + fromHeaders: (headers) => ({ + version: toForkName(headers.getRequired(MetaHeader.Version)), + }), +}; + +export const ExecutionOptimisticAndVersionCodec: ResponseMetadataCodec = { + toJson: (val) => ExecutionOptimisticAndVersionType.toJson(val), + fromJson: (val) => ExecutionOptimisticAndVersionType.fromJson(val), + toHeadersObject: (val) => ({ + [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), + [MetaHeader.Version]: val.version, + }), + fromHeaders: (headers) => ({ + executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), + version: toForkName(headers.getRequired(MetaHeader.Version)), + }), +}; + +export const ExecutionOptimisticAndFinalizedCodec: ResponseMetadataCodec = { + toJson: (val) => ExecutionOptimisticAndFinalizedType.toJson(val), + fromJson: (val) => ExecutionOptimisticAndFinalizedType.fromJson(val), + toHeadersObject: (val) => ({ + [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), + [MetaHeader.Finalized]: val.finalized.toString(), + }), + fromHeaders: (headers) => ({ + executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), + finalized: toBoolean(headers.getOrDefault(MetaHeader.Finalized, "false")), + }), +}; + +export const ExecutionOptimisticFinalizedAndVersionCodec: ResponseMetadataCodec = + { + toJson: (val) => ExecutionOptimisticFinalizedAndVersionType.toJson(val), + fromJson: (val) => ExecutionOptimisticFinalizedAndVersionType.fromJson(val), + toHeadersObject: (val) => ({ + [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), + [MetaHeader.Finalized]: val.finalized.toString(), + [MetaHeader.Version]: val.version, + }), + fromHeaders: (headers) => ({ + executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), + finalized: toBoolean(headers.getOrDefault(MetaHeader.Finalized, "false")), + version: toForkName(headers.getRequired(MetaHeader.Version)), + }), + }; + +export const ExecutionOptimisticAndDependentRootCodec: ResponseMetadataCodec = + { + toJson: (val) => ExecutionOptimisticAndDependentRootType.toJson(val), + fromJson: (val) => ExecutionOptimisticAndDependentRootType.fromJson(val), + toHeadersObject: (val) => ({ + [MetaHeader.ExecutionOptimistic]: val.executionOptimistic.toString(), + [MetaHeader.DependentRoot]: val.dependentRoot, + }), + fromHeaders: (headers) => ({ + executionOptimistic: toBoolean(headers.getOrDefault(MetaHeader.ExecutionOptimistic, "false")), + dependentRoot: headers.getRequired(MetaHeader.DependentRoot), + }), + }; diff --git a/packages/api/src/utils/routes.ts b/packages/api/src/utils/routes.ts deleted file mode 100644 index 77d177f7b24c..000000000000 --- a/packages/api/src/utils/routes.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {allForks, ssz} from "@lodestar/types"; -import {ForkBlobs} from "@lodestar/params"; - -import {TypeJson} from "./types.js"; - -/* eslint-disable @typescript-eslint/naming-convention */ - -export function allForksSignedBlockContentsReqSerializer( - blockSerializer: (data: allForks.SignedBeaconBlock) => TypeJson -): TypeJson { - return { - toJson: (data) => ({ - signed_block: blockSerializer(data.signedBlock).toJson(data.signedBlock), - kzg_proofs: ssz.deneb.KZGProofs.toJson(data.kzgProofs), - blobs: ssz.deneb.Blobs.toJson(data.blobs), - }), - - fromJson: (data: {signed_block: unknown; kzg_proofs: unknown; blobs: unknown}) => ({ - signedBlock: blockSerializer(data.signed_block as allForks.SignedBeaconBlock).fromJson(data.signed_block), - kzgProofs: ssz.deneb.KZGProofs.fromJson(data.kzg_proofs), - blobs: ssz.deneb.Blobs.fromJson(data.blobs), - }), - }; -} - -export function allForksBlockContentsResSerializer(fork: ForkBlobs): TypeJson { - return { - toJson: (data) => ({ - block: (ssz.allForks[fork].BeaconBlock as allForks.AllForksSSZTypes["BeaconBlock"]).toJson(data.block), - kzg_proofs: ssz.deneb.KZGProofs.toJson(data.kzgProofs), - blobs: ssz.deneb.Blobs.toJson(data.blobs), - }), - fromJson: (data: {block: unknown; blob_sidecars: unknown; kzg_proofs: unknown; blobs: unknown}) => ({ - block: ssz.allForks[fork].BeaconBlock.fromJson(data.block), - kzgProofs: ssz.deneb.KZGProofs.fromJson(data.kzg_proofs), - blobs: ssz.deneb.Blobs.fromJson(data.blobs), - }), - }; -} diff --git a/packages/api/src/utils/schema.ts b/packages/api/src/utils/schema.ts index 6b08f27bdbab..2d086fd8dfa9 100644 --- a/packages/api/src/utils/schema.ts +++ b/packages/api/src/utils/schema.ts @@ -1,26 +1,23 @@ -import {ReqGeneric} from "./types.js"; +import {Endpoint, HeaderParams, PathParams, QueryParams} from "./types.js"; // Reasoning: Allows to declare JSON schemas for server routes in a succinct typesafe way. // The enums exposed here are very feature incomplete but cover the minimum necessary for // the existing routes. Since the arguments for Ethereum Consensus server routes are very simple it suffice. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type JsonSchema = Record; +type JsonSchema = Record; type JsonSchemaObj = { type: "object"; required: string[]; properties: Record; }; +type RequireSchema = {[K in keyof T]-?: Schema}; -export type SchemaDefinition = { - params?: { - [K in keyof ReqType["params"]]: Schema; - }; - query?: { - [K in keyof ReqType["query"]]: Schema; - }; - body?: Schema; -}; +export type SchemaDefinition = (ReqType["params"] extends PathParams + ? {params: RequireSchema} + : {params?: never}) & + (ReqType["query"] extends QueryParams ? {query: RequireSchema} : {query?: never}) & + (ReqType["headers"] extends HeaderParams ? {headers: RequireSchema} : {headers?: never}) & + (ReqType extends {body: unknown} ? {body: Schema} : {body?: never}); export enum Schema { Uint, @@ -29,6 +26,7 @@ export enum Schema { String, StringRequired, StringArray, + StringArrayRequired, UintOrStringRequired, UintOrStringArray, Object, @@ -54,6 +52,7 @@ function getJsonSchemaItem(schema: Schema): JsonSchema { return {type: "string"}; case Schema.StringArray: + case Schema.StringArrayRequired: return {type: "array", items: {type: "string"}}; case Schema.UintOrStringRequired: @@ -80,6 +79,7 @@ function isRequired(schema: Schema): boolean { case Schema.UintRequired: case Schema.StringRequired: case Schema.UintOrStringRequired: + case Schema.StringArrayRequired: return true; default: @@ -87,34 +87,45 @@ function isRequired(schema: Schema): boolean { } } -export function getFastifySchema(schemaDef: SchemaDefinition): JsonSchema { - const schema: {params?: JsonSchemaObj; querystring?: JsonSchemaObj; body?: JsonSchema} = {}; +export function getFastifySchema(schemaDef: SchemaDefinition): JsonSchema { + const schema: {params?: JsonSchemaObj; querystring?: JsonSchemaObj; headers?: JsonSchemaObj; body?: JsonSchema} = {}; if (schemaDef.body != null) { schema.body = getJsonSchemaItem(schemaDef.body); } if (schemaDef.params) { - schema.params = {type: "object", required: [] as string[], properties: {}}; + schema.params = {type: "object", required: [], properties: {}}; - for (const [key, def] of Object.entries(schemaDef.params)) { - schema.params.properties[key] = getJsonSchemaItem(def as Schema); - if (isRequired(def as Schema)) { + for (const [key, def] of Object.entries(schemaDef.params)) { + schema.params.properties[key] = getJsonSchemaItem(def); + if (isRequired(def)) { schema.params.required.push(key); } } } if (schemaDef.query) { - schema.querystring = {type: "object", required: [] as string[], properties: {}}; + schema.querystring = {type: "object", required: [], properties: {}}; - for (const [key, def] of Object.entries(schemaDef.query)) { - schema.querystring.properties[key] = getJsonSchemaItem(def as Schema); - if (isRequired(def as Schema)) { + for (const [key, def] of Object.entries(schemaDef.query)) { + schema.querystring.properties[key] = getJsonSchemaItem(def); + if (isRequired(def)) { schema.querystring.required.push(key); } } } + if (schemaDef.headers) { + schema.headers = {type: "object", required: [], properties: {}}; + + for (const [key, def] of Object.entries(schemaDef.headers)) { + schema.headers.properties[key] = getJsonSchemaItem(def); + if (isRequired(def)) { + schema.headers.required.push(key); + } + } + } + return schema; } diff --git a/packages/api/src/utils/serdes.ts b/packages/api/src/utils/serdes.ts index 44e52a93382e..73196c917a66 100644 --- a/packages/api/src/utils/serdes.ts +++ b/packages/api/src/utils/serdes.ts @@ -67,6 +67,14 @@ export function toU64StrOpt(u64: U64 | undefined): U64Str | undefined { return u64 !== undefined ? toU64Str(u64) : undefined; } +export function toValidatorIdsStr(ids?: (string | number)[]): string[] | undefined { + return ids?.map((id) => (typeof id === "string" ? id : toU64Str(id))); +} + +export function fromValidatorIdsStr(ids?: string[]): (string | number)[] | undefined { + return ids?.map((id) => (typeof id === "string" && id.startsWith("0x") ? id : fromU64Str(id))); +} + const GRAFFITI_HEX_LENGTH = 66; export function toGraffitiHex(utf8: string): string { @@ -93,3 +101,13 @@ export function fromGraffitiHex(hex: string): string { return hex; } } + +export function toBoolean(value: string): boolean { + value = value.toLowerCase(); + + if (value !== "true" && value !== "false") { + throw Error(`Invalid boolean ${value}`); + } + + return value === "true"; +} diff --git a/packages/api/src/utils/server/errors.ts b/packages/api/src/utils/server/error.ts similarity index 76% rename from packages/api/src/utils/server/errors.ts rename to packages/api/src/utils/server/error.ts index ea075678f4f6..b57a4c402a90 100644 --- a/packages/api/src/utils/server/errors.ts +++ b/packages/api/src/utils/server/error.ts @@ -1,4 +1,4 @@ -import {HttpErrorCodes} from "../client/httpStatusCode.js"; +import {HttpErrorCodes} from "../httpStatusCode.js"; export class ApiError extends Error { statusCode: HttpErrorCodes; diff --git a/packages/api/src/utils/server/genericJsonServer.ts b/packages/api/src/utils/server/genericJsonServer.ts deleted file mode 100644 index ebda05ccc859..000000000000 --- a/packages/api/src/utils/server/genericJsonServer.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type {FastifyInstance} from "fastify"; -import {mapValues} from "@lodestar/utils"; -import {ChainForkConfig} from "@lodestar/config"; -import {ReqGeneric, TypeJson, Resolves, RouteGroupDefinition} from "../types.js"; -import {getFastifySchema} from "../schema.js"; -import {toColonNotationPath} from "../urlFormat.js"; -import {APIServerHandler} from "../../interfaces.js"; -import {ServerRoute} from "./types.js"; - -// See /packages/api/src/routes/index.ts for reasoning - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -export type ServerRoutes< - Api extends Record, - ReqTypes extends {[K in keyof Api]: ReqGeneric}, -> = { - [K in keyof Api]: ServerRoute; -}; - -export function getGenericJsonServer< - Api extends Record, - ReqTypes extends {[K in keyof Api]: ReqGeneric}, ->( - {routesData, getReqSerializers, getReturnTypes}: RouteGroupDefinition, - config: ChainForkConfig, - api: Api -): ServerRoutes { - const reqSerializers = getReqSerializers(config); - const returnTypes = getReturnTypes(config); - - return mapValues(routesData, (routeDef, routeId) => { - const routeSerdes = reqSerializers[routeId]; - const returnType = returnTypes[routeId as keyof typeof returnTypes] as TypeJson | null; - - return { - // Convert '/states/{state_id}' into '/states/:state_id' - url: toColonNotationPath(routeDef.url), - method: routeDef.method, - id: routeId as string, - schema: routeSerdes.schema && getFastifySchema(routeSerdes.schema), - - handler: async function handler(this: FastifyInstance, req, resp): Promise { - const args: any[] = routeSerdes.parseReq(req as ReqGeneric as ReqTypes[keyof Api]); - const data = (await api[routeId](...args)) as Resolves; - - if (routeDef.statusOk !== undefined) { - resp.statusCode = routeDef.statusOk; - } - - if (returnType) { - return returnType.toJson(data); - } else { - return {}; - } - }, - }; - }); -} diff --git a/packages/api/src/utils/server/handler.ts b/packages/api/src/utils/server/handler.ts new file mode 100644 index 000000000000..162f68f25c05 --- /dev/null +++ b/packages/api/src/utils/server/handler.ts @@ -0,0 +1,146 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import type * as fastify from "fastify"; +import {HttpHeader, MediaType, SUPPORTED_MEDIA_TYPES, parseAcceptHeader, parseContentTypeHeader} from "../headers.js"; +import { + Endpoint, + RequestData, + JsonRequestData, + JsonRequestMethods, + RequestWithBodyCodec, + RouteDefinition, + SszRequestData, + SszRequestMethods, + isRequestWithoutBody, +} from "../types.js"; +import {WireFormat, fromWireFormat, getWireFormat} from "../wireFormat.js"; +import {ApiError} from "./error.js"; +import {ApplicationMethod, ApplicationResponse} from "./method.js"; + +export type FastifyHandler = fastify.RouteHandlerMethod< + fastify.RawServerDefault, + fastify.RawRequestDefaultExpression, + fastify.RawReplyDefaultExpression, + { + Body: E["request"] extends JsonRequestData ? E["request"]["body"] : undefined; + Querystring: E["request"]["query"]; + Params: E["request"]["params"]; + Headers: E["request"]["headers"]; + }, + fastify.ContextConfigDefault +>; + +export function createFastifyHandler( + definition: RouteDefinition, + method: ApplicationMethod, + _operationId: string +): FastifyHandler { + return async (req, resp) => { + // Determine response wire format first to inform application method + // about the preferable return type to avoid unnecessary serialization + let responseMediaType: MediaType | null; + + const acceptHeader = req.headers.accept; + if (definition.resp.isEmpty) { + // Ignore Accept header, the response will be sent without body + responseMediaType = null; + } else if (acceptHeader === undefined || acceptHeader === "*/*") { + // Default to json to not force user to set header, e.g. when using curl + responseMediaType = MediaType.json; + } else { + const {onlySupport} = definition.resp; + const supportedMediaTypes = onlySupport !== undefined ? [fromWireFormat(onlySupport)] : SUPPORTED_MEDIA_TYPES; + responseMediaType = parseAcceptHeader(acceptHeader, supportedMediaTypes); + + if (responseMediaType === null) { + throw new ApiError(406, `Accepted media types not supported: ${acceptHeader}`); + } + } + const responseWireFormat = responseMediaType !== null ? getWireFormat(responseMediaType) : null; + + let response: ApplicationResponse; + try { + if (isRequestWithoutBody(definition)) { + response = await method(definition.req.parseReq(req as RequestData), { + sszBytes: null, + returnBytes: responseWireFormat === WireFormat.ssz, + }); + } else { + const contentType = req.headers[HttpHeader.ContentType]; + if (contentType === undefined) { + throw new ApiError(400, "Content-Type header is required"); + } + const requestMediaType = parseContentTypeHeader(contentType); + if (requestMediaType === null) { + throw new ApiError(415, `Unsupported media type: ${contentType.split(";", 1)[0]}`); + } + const requestWireFormat = getWireFormat(requestMediaType); + + const {onlySupport} = definition.req as RequestWithBodyCodec; + if (onlySupport !== undefined && onlySupport !== requestWireFormat) { + throw new ApiError(415, `Endpoint only supports ${onlySupport.toUpperCase()} requests`); + } + + switch (requestWireFormat) { + case WireFormat.json: + response = await method((definition.req as JsonRequestMethods).parseReqJson(req as JsonRequestData), { + sszBytes: null, + returnBytes: responseWireFormat === WireFormat.ssz, + }); + break; + case WireFormat.ssz: + response = await method( + (definition.req as SszRequestMethods).parseReqSsz(req as SszRequestData), + { + sszBytes: req.body as Uint8Array, + returnBytes: responseWireFormat === WireFormat.ssz, + } + ); + break; + } + } + } catch (e) { + if (e instanceof ApiError) throw e; + // Errors related to parsing should return 400 status code + throw new ApiError(400, (e as Error).message); + } + + if (response?.status !== undefined) { + resp.statusCode = response.status; + } + + switch (responseWireFormat) { + case WireFormat.json: { + const metaHeaders = definition.resp.meta.toHeadersObject(response?.meta); + metaHeaders[HttpHeader.ContentType] = MediaType.json; + void resp.headers(metaHeaders); + const data = + response?.data instanceof Uint8Array + ? definition.resp.data.toJson(definition.resp.data.deserialize(response.data, response.meta), response.meta) + : definition.resp.data.toJson(response?.data, response?.meta); + const metaJson = definition.resp.meta.toJson(response?.meta); + if (definition.resp.transform) { + return definition.resp.transform.toResponse(data, metaJson); + } + return { + data, + ...(metaJson as object), + }; + } + case WireFormat.ssz: { + const metaHeaders = definition.resp.meta.toHeadersObject(response?.meta); + metaHeaders[HttpHeader.ContentType] = MediaType.ssz; + void resp.headers(metaHeaders); + const data = + response?.data instanceof Uint8Array + ? response.data + : definition.resp.data.serialize(response?.data, response?.meta); + // Fastify supports returning `Uint8Array` from handler and will efficiently + // convert it to a `Buffer` internally without copying the underlying `ArrayBuffer` + return data; + } + case null: + // Send response without body + return; + } + }; +} diff --git a/packages/api/src/utils/server/index.ts b/packages/api/src/utils/server/index.ts index 2e17e9ee2a6f..3f3b705b5bcf 100644 --- a/packages/api/src/utils/server/index.ts +++ b/packages/api/src/utils/server/index.ts @@ -1,4 +1,5 @@ -export * from "./genericJsonServer.js"; -export * from "./registerRoute.js"; -export * from "./errors.js"; -export * from "./types.js"; +export * from "./error.js"; +export * from "./handler.js"; +export * from "./method.js"; +export * from "./parser.js"; +export * from "./route.js"; diff --git a/packages/api/src/utils/server/method.ts b/packages/api/src/utils/server/method.ts new file mode 100644 index 000000000000..080c78869798 --- /dev/null +++ b/packages/api/src/utils/server/method.ts @@ -0,0 +1,39 @@ +import {EmptyMeta, EmptyResponseData} from "../codecs.js"; +import {HttpSuccessCodes} from "../httpStatusCode.js"; +import {Endpoint, HasOnlyOptionalProps} from "../types.js"; + +type ApplicationResponseObject = { + /** + * Set non-200 success status code + */ + status?: HttpSuccessCodes; +} & (E["return"] extends EmptyResponseData + ? {data?: never} + : {data: E["return"] | (E["return"] extends undefined ? undefined : Uint8Array)}) & + (E["meta"] extends EmptyMeta ? {meta?: never} : {meta: E["meta"]}); + +export type ApplicationResponse = + HasOnlyOptionalProps> extends true + ? ApplicationResponseObject | void + : ApplicationResponseObject; + +export type ApiContext = { + /** + * Raw ssz bytes from request payload, only available for ssz requests + */ + sszBytes?: Uint8Array | null; + /** + * Informs application method about preferable return type to avoid unnecessary serialization + */ + returnBytes?: boolean; +}; + +type GenericOptions = Record; + +export type ApplicationMethod = ( + args: E["args"], + context?: ApiContext, + opts?: GenericOptions +) => Promise>; + +export type ApplicationMethods> = {[K in keyof Es]: ApplicationMethod}; diff --git a/packages/api/src/utils/server/parser.ts b/packages/api/src/utils/server/parser.ts new file mode 100644 index 000000000000..fd668b63757e --- /dev/null +++ b/packages/api/src/utils/server/parser.ts @@ -0,0 +1,27 @@ +import type * as fastify from "fastify"; +import {MediaType} from "../headers.js"; + +export function addSszContentTypeParser(server: fastify.FastifyInstance): void { + // Cache body schema symbol, does not change per request + let bodySchemaSymbol: symbol | undefined; + + server.addContentTypeParser( + MediaType.ssz, + {parseAs: "buffer"}, + async (request: fastify.FastifyRequest, payload: Buffer) => { + if (bodySchemaSymbol === undefined) { + // Get body schema symbol to be able to access validation function + // https://github.com/fastify/fastify/blob/af2ccb5ff681c1d0ac22eb7314c6fa803f73c873/lib/symbols.js#L25 + bodySchemaSymbol = Object.getOwnPropertySymbols(request.context).find((s) => s.description === "body-schema"); + } + // JSON schema validation will be applied to `Buffer` object, it is required to override validation function + // See https://github.com/fastify/help/issues/1012, it is not possible right now to define a schema per content type + (request.context as unknown as Record)[bodySchemaSymbol as symbol] = () => true; + + // We could just return the `Buffer` here which is a subclass of `Uint8Array` but downstream code does not require it + // and it's better to convert it here to avoid unexpected behavior such as `Buffer.prototype.slice` not copying memory + // See https://github.com/nodejs/node/issues/41588#issuecomment-1016269584 + return new Uint8Array(payload.buffer, payload.byteOffset, payload.byteLength); + } + ); +} diff --git a/packages/api/src/utils/server/registerRoute.ts b/packages/api/src/utils/server/registerRoute.ts deleted file mode 100644 index 9d9bd9016a3d..000000000000 --- a/packages/api/src/utils/server/registerRoute.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {ServerInstance, RouteConfig, ServerRoute} from "./types.js"; - -export function registerRoute( - server: ServerInstance, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - route: ServerRoute, - namespace?: string -): void { - server.route({ - url: route.url, - method: route.method, - handler: route.handler, - // append the namespace as a tag for downstream consumption of our API schema, eg: for swagger UI - schema: {...route.schema, ...(namespace ? {tags: [namespace]} : undefined), operationId: route.id}, - config: {operationId: route.id} as RouteConfig, - }); -} diff --git a/packages/api/src/utils/server/route.ts b/packages/api/src/utils/server/route.ts new file mode 100644 index 000000000000..aa04a1f11cf7 --- /dev/null +++ b/packages/api/src/utils/server/route.ts @@ -0,0 +1,45 @@ +import type * as fastify from "fastify"; +import {mapValues} from "@lodestar/utils"; +import {getFastifySchema} from "../schema.js"; +import {Endpoint, RouteDefinition, RouteDefinitions} from "../types.js"; +import {toColonNotationPath} from "../urlFormat.js"; +import {FastifyHandler, createFastifyHandler} from "./handler.js"; +import {ApplicationMethod, ApplicationMethods} from "./method.js"; + +export type FastifySchema = fastify.FastifySchema & { + operationId: string; + tags?: string[]; +}; + +export type FastifyRoute = { + url: string; + method: fastify.HTTPMethods; + handler: FastifyHandler; + schema: FastifySchema; +}; +export type FastifyRoutes> = {[K in keyof Es]: FastifyRoute}; + +export function createFastifyRoute( + definition: RouteDefinition, + method: ApplicationMethod, + operationId: string +): FastifyRoute { + return { + url: toColonNotationPath(definition.url), + method: definition.method, + handler: createFastifyHandler(definition, method, operationId), + schema: { + ...getFastifySchema(definition.req.schema), + operationId, + }, + }; +} + +export function createFastifyRoutes>( + definitions: RouteDefinitions, + methods: ApplicationMethods +): FastifyRoutes { + return mapValues(definitions, (definition, operationId) => + createFastifyRoute(definition, methods?.[operationId]?.bind(methods), operationId as string) + ); +} diff --git a/packages/api/src/utils/server/types.ts b/packages/api/src/utils/server/types.ts deleted file mode 100644 index ba5f54cc96a3..000000000000 --- a/packages/api/src/utils/server/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type {FastifyInstance, FastifyContextConfig} from "fastify"; -import type * as fastify from "fastify"; -import {ReqGeneric} from "../types.js"; - -export type ServerInstance = FastifyInstance; - -export type RouteConfig = FastifyContextConfig & { - operationId: ServerRoute["id"]; -}; - -export type ServerRoute = { - url: string; - method: fastify.HTTPMethods; - handler: FastifyHandler; - schema?: fastify.FastifySchema; - /** OperationId as defined in https://github.com/ethereum/beacon-APIs/blob/v2.1.0/apis/beacon/blocks/attestations.yaml#L2 */ - id: string; -}; - -/* eslint-disable @typescript-eslint/naming-convention */ - -/** Adaptor for Fastify v3.x.x route type which has a ton of arguments */ -export type FastifyHandler = fastify.RouteHandlerMethod< - fastify.RawServerDefault, - fastify.RawRequestDefaultExpression, - fastify.RawReplyDefaultExpression, - { - Body: Req["body"]; - Querystring: Req["query"]; - Params: Req["params"]; - }, - fastify.ContextConfigDefault ->; diff --git a/packages/api/src/utils/types.ts b/packages/api/src/utils/types.ts index 999fd93bdc81..abe2f358320d 100644 --- a/packages/api/src/utils/types.ts +++ b/packages/api/src/utils/types.ts @@ -1,253 +1,161 @@ -import {isBasicType, ListBasicType, Type, isCompositeType, ListCompositeType, ArrayType} from "@chainsafe/ssz"; -import {ForkName} from "@lodestar/params"; -import {ChainForkConfig} from "@lodestar/config"; -import {objectToExpectedCase} from "@lodestar/utils"; -import {APIClientHandler, ApiClientResponseData, APIServerHandler, ClientApi} from "../interfaces.js"; -import {Schema, SchemaDefinition} from "./schema.js"; - -// See /packages/api/src/routes/index.ts for reasoning - -/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any */ - -/** All JSON inside the JS code must be camel case */ -const codeCase = "camel" as const; - -export type RouteGroupDefinition< - Api extends Record, - ReqTypes extends {[K in keyof Api]: ReqGeneric}, +import {ExtraRequestInit} from "./client/request.js"; +import {EmptyMeta} from "./codecs.js"; +import {HeadersExtra} from "./headers.js"; +import {SchemaDefinition} from "./schema.js"; +import {WireFormat} from "./wireFormat.js"; + +export type HasOnlyOptionalProps = { + [K in keyof T]-?: object extends Pick ? never : K; +} extends {[_ in keyof T]: never} + ? true + : false; + +export type PathParams = Record; +export type QueryParams = Record; +export type HeaderParams = Record; + +export type RequestData< + P extends PathParams = PathParams, + Q extends QueryParams = QueryParams, + H extends HeaderParams = HeaderParams, > = { - routesData: RoutesData; - getReqSerializers: (config: ChainForkConfig) => ReqSerializers; - getReturnTypes: (config: ChainForkConfig) => ReturnTypes>; + params?: P; + query?: Q; + headers?: H; }; -export type RouteDef = { - url: string; - method: "GET" | "POST" | "DELETE"; - statusOk?: number; +export type JsonRequestData< + B = unknown, + P extends PathParams = PathParams, + Q extends QueryParams = QueryParams, + H extends HeaderParams = HeaderParams, +> = RequestData & { + body?: B; }; -export type ReqGeneric = { - params?: Record; - query?: Record; - body?: any; - headers?: Record; -}; +export type SszRequestData

= Omit & + ("body" extends keyof P ? (P["body"] extends void ? {body?: never} : {body: Uint8Array}) : {body?: never}); -export type ReqEmpty = ReqGeneric; -export type Resolves any> = Awaited>; +export type HttpMethod = "GET" | "POST" | "DELETE"; -export type TypeJson = { - toJson(val: T): unknown; - fromJson(json: unknown): T; +/** + * This type describes the general shape of a route + * + * This includes both http and application-level shape + * - The http method + * - Used to more strictly enforce the shape of the request + * - The application-level parameters + * - this enforces the shape of the input data passed by the client and to the route handler + * - The http request + * - this enforces the shape of the querystring, url params, request body + * - The application-level return data + * - this enforces the shape of the output data passed back to the client and returned by the route handler + * - The application-level return metadata + * - this enforces the shape of the returned metadata, used informationally and to help decode the return data + */ +export type Endpoint< + Method extends HttpMethod = HttpMethod, + ArgsType = unknown, + RequestType extends Method extends "GET" ? RequestData : JsonRequestData = JsonRequestData, + ReturnType = unknown, + Meta = unknown, +> = { + method: Method; + /** The parameters the client passes / server app code ingests */ + args: ArgsType; + /** The parameters in the http request */ + request: RequestType; + /** The return data */ + return: ReturnType; + /** The return metadata */ + meta: Meta; }; -// -// REQ -// +// Request codec -export type ReqSerializer any, ReqType extends ReqGeneric> = { - writeReq: (...args: Parameters) => ReqType; - parseReq: (arg: ReqType) => Parameters; - schema?: SchemaDefinition; +/** Encode / decode requests to & from function params, as well as schema definitions */ +export type RequestWithoutBodyCodec = { + writeReq: (p: E["args"]) => E["request"]; // client + parseReq: (r: E["request"]) => E["args"]; // server + schema: SchemaDefinition; }; -export type ReqSerializers< - Api extends Record, - ReqTypes extends {[K in keyof Api]: ReqGeneric}, -> = { - [K in keyof Api]: ReqSerializer; +export type JsonRequestMethods = { + writeReqJson: (p: E["args"]) => E["request"]; // client + parseReqJson: (r: E["request"]) => E["args"]; // server }; -/** Curried definition to infer only one of the two generic types */ -export type ReqGenArg any, ReqType extends ReqGeneric> = ReqSerializer; - -// -// Helpers -// - -/** Shortcut for routes that have no params, query nor body */ -export const reqEmpty: ReqSerializer<() => void, ReqEmpty> = { - writeReq: () => ({}), - parseReq: () => [] as [], +export type SszRequestMethods = { + writeReqSsz: (p: E["args"]) => SszRequestData; // client + parseReqSsz: (r: SszRequestData) => E["args"]; // server }; -/** Shortcut for routes that have only body */ -export const reqOnlyBody = ( - type: TypeJson, - bodySchema: Schema -): ReqGenArg<(arg: T) => Promise, {body: unknown}> => ({ - writeReq: (items) => ({body: type.toJson(items)}), - parseReq: ({body}) => [type.fromJson(body)], - schema: {body: bodySchema}, -}); - -/** SSZ factory helper + typed. limit = 1e6 as a big enough random number */ -export function ArrayOf(elementType: Type): ArrayType, unknown, unknown> { - if (isCompositeType(elementType)) { - return new ListCompositeType(elementType, Infinity) as unknown as ArrayType, unknown, unknown>; - } else if (isBasicType(elementType)) { - return new ListBasicType(elementType, Infinity) as unknown as ArrayType, unknown, unknown>; - } else { - throw Error(`Unknown type ${elementType.typeName}`); - } -} - -/** - * SSZ factory helper + typed to return responses of type - * ``` - * data: T - * ``` - */ -export function ContainerData(dataType: TypeJson): TypeJson<{data: T}> { - return { - toJson: ({data}) => ({ - data: dataType.toJson(data), - }), - fromJson: ({data}: {data: unknown}) => { - return { - data: dataType.fromJson(data), - }; - }, +export type RequestWithBodyCodec = JsonRequestMethods & + SszRequestMethods & { + schema: SchemaDefinition; + /** Support ssz-only or json-only requests */ + onlySupport?: WireFormat; }; -} /** - * SSZ factory helper + typed to return responses of type `{data: T; executionOptimistic: boolean}` + * Handles translation between `Endpoint["args"]` and `Endpoint["request"]` */ -export function ContainerDataExecutionOptimistic( - dataType: TypeJson -): TypeJson<{data: T; executionOptimistic: boolean}> { - return { - toJson: ({data, executionOptimistic}) => ({ - data: dataType.toJson(data), - execution_optimistic: executionOptimistic, - }), - fromJson: ({data, execution_optimistic}: {data: unknown; execution_optimistic: boolean}) => { - return { - data: dataType.fromJson(data), - executionOptimistic: execution_optimistic, - }; - }, - }; +export type RequestCodec = E["method"] extends "GET" + ? RequestWithoutBodyCodec + : "body" extends keyof E["request"] + ? RequestWithBodyCodec + : RequestWithoutBodyCodec; + +export function isRequestWithoutBody( + definition: RouteDefinition +): definition is RouteDefinition & {req: RequestWithoutBodyCodec} { + return definition.method === "GET" || definition.req.schema.body === undefined; } -/** - * SSZ factory helper + typed to return responses of type - * ``` - * data: T - * version: ForkName - * ``` - */ -export function WithVersion(getType: (fork: ForkName) => TypeJson): TypeJson<{data: T; version: ForkName}> { - return { - toJson: ({data, version}) => ({ - data: getType(version ?? ForkName.phase0).toJson(data), - version, - }), - fromJson: ({data, version}: {data: unknown; version: string}) => { - // Teku returns fork as UPPERCASE - version = version.toLowerCase(); - - // Un-safe external data, validate version is known ForkName value - if (!(version in ForkName)) throw Error(`Invalid version ${version}`); - - return { - data: getType(version as ForkName).fromJson(data), - version: version as ForkName, - }; - }, - }; -} +// Response codec -/** - * SSZ factory helper to wrap an existing type with `{executionOptimistic: boolean}` - */ -export function WithExecutionOptimistic( - type: TypeJson -): TypeJson { - return { - toJson: ({executionOptimistic, ...data}) => ({ - ...(type.toJson(data as unknown as T) as Record), - execution_optimistic: executionOptimistic, - }), - fromJson: ({execution_optimistic, ...data}: T & {execution_optimistic: boolean}) => ({ - ...type.fromJson(data), - executionOptimistic: execution_optimistic, - }), - }; -} +export type ResponseDataCodec = { + toJson: (data: T, meta: M) => unknown; // server + fromJson: (data: unknown, meta: M) => T; // client + serialize: (data: T, meta: M) => Uint8Array; // server + deserialize: (data: Uint8Array, meta: M) => T; // client +}; -/** - * SSZ factory helper to wrap an existing type with `{finalized: boolean}` - */ -export function WithFinalized(type: TypeJson): TypeJson { - return { - toJson: ({finalized, ...data}) => ({ - ...(type.toJson(data as unknown as T) as Record), - finalized, - }), - fromJson: ({finalized, ...data}: T & {finalized: boolean}) => ({ - ...type.fromJson(data), - finalized, - }), +export type ResponseMetadataCodec = { + toJson: (val: T) => unknown; // server + fromJson: (val: unknown) => T; // client + toHeadersObject: (val: T) => Record; // server + fromHeaders: (headers: HeadersExtra) => T; // server +}; + +export type ResponseCodec = { + data: ResponseDataCodec; + meta: ResponseMetadataCodec; + /** Occasionally, json responses require an extra transformation to separate the data from metadata */ + transform?: { + toResponse: (data: unknown, meta: unknown) => unknown; + fromResponse: (resp: unknown) => { + data: E["return"]; + } & (E["meta"] extends EmptyMeta ? {meta?: never} : {meta: E["meta"]}); }; -} + /** Support ssz-only or json-only responses */ + onlySupport?: WireFormat; + /** Indicator used to handle empty responses */ + isEmpty?: true; +}; /** - * SSZ factory helper to wrap an existing type with `{executionPayloadValue: Wei, consensusBlockValue: Wei}` + * Top-level definition of a route used by both the client and server + * - url and method + * - request and response codec + * - request json schema */ -export function WithBlockValues( - type: TypeJson -): TypeJson { - return { - toJson: ({executionPayloadValue, consensusBlockValue, ...data}) => ({ - ...(type.toJson(data as unknown as T) as Record), - execution_payload_value: executionPayloadValue.toString(), - consensus_block_value: consensusBlockValue.toString(), - }), - fromJson: ({ - execution_payload_value, - consensus_block_value, - ...data - }: T & {execution_payload_value: string; consensus_block_value: string}) => ({ - ...type.fromJson(data), - // For cross client usage where beacon or validator are of separate clients, executionPayloadValue could be missing - executionPayloadValue: BigInt(execution_payload_value ?? "0"), - consensusBlockValue: BigInt(consensus_block_value ?? "0"), - }), - }; -} - -type JsonCase = "snake" | "constant" | "camel" | "param" | "header" | "pascal" | "dot" | "notransform"; - -/** Helper to only translate casing */ -export function jsonType | Record[] | unknown[]>( - jsonCase: JsonCase -): TypeJson { - return { - toJson: (val: T) => objectToExpectedCase(val as Record, jsonCase), - fromJson: (json) => objectToExpectedCase(json as Record, codeCase) as T, - }; -} - -/** Helper to not do any transformation with the type */ -export function sameType(): TypeJson { - return { - toJson: (val) => val as unknown, - fromJson: (json) => json as T, - }; -} - -// -// RETURN -// -export type KeysOfNonVoidResolveValues> = { - [K in keyof Api]: ApiClientResponseData> extends void ? never : K; -}[keyof Api]; - -export type ReturnTypes> = { - [K in keyof Pick>]: TypeJson>>; +export type RouteDefinition = { + url: string; + method: E["method"]; + req: RequestCodec; + resp: ResponseCodec; + init?: ExtraRequestInit; }; -export type RoutesData> = {[K in keyof Api]: RouteDef}; +export type RouteDefinitions> = {[K in keyof Es]: RouteDefinition}; diff --git a/packages/api/src/utils/urlFormat.ts b/packages/api/src/utils/urlFormat.ts index efbbb1d6be39..5aac4aebb156 100644 --- a/packages/api/src/utils/urlFormat.ts +++ b/packages/api/src/utils/urlFormat.ts @@ -53,7 +53,7 @@ export function urlToTokens(path: string): Token[] { } /** - * Compile a route URL formater with syntax `/path/{var1}/{var2}`. + * Compile a route URL formatter with syntax `/path/{var1}/{var2}`. * Returns a function that expects an object `{var1: 1, var2: 2}`, and returns`/path/1/2`. * * It's cheap enough to be negligible. For the sample input below it costs: @@ -62,7 +62,7 @@ export function urlToTokens(path: string): Token[] { * - execute with template literal: 12 ns / op * @param path `/eth/v1/validator/:name/attester/:epoch` */ -export function compileRouteUrlFormater(path: string): (arg: Args) => string { +export function compileRouteUrlFormatter(path: string): (arg: Args) => string { const tokens = urlToTokens(path); // Return a faster function if there's not ':' token @@ -82,7 +82,7 @@ export function compileRouteUrlFormater(path: string): (arg: Args) => string { } }); - return function urlFormater(args: Args) { + return function urlFormatter(args: Args) { // Don't use .map() or .join(), it's x3 slower let s = ""; for (const fn of fns) s += fn(args); diff --git a/packages/api/src/utils/wireFormat.ts b/packages/api/src/utils/wireFormat.ts new file mode 100644 index 000000000000..cd8fd05e0cb2 --- /dev/null +++ b/packages/api/src/utils/wireFormat.ts @@ -0,0 +1,24 @@ +import {MediaType} from "./headers.js"; + +export enum WireFormat { + json = "json", + ssz = "ssz", +} + +export function getWireFormat(mediaType: MediaType): WireFormat { + switch (mediaType) { + case MediaType.json: + return WireFormat.json; + case MediaType.ssz: + return WireFormat.ssz; + } +} + +export function fromWireFormat(wireFormat: WireFormat): MediaType { + switch (wireFormat) { + case WireFormat.json: + return MediaType.json; + case WireFormat.ssz: + return MediaType.ssz; + } +} diff --git a/packages/api/test/perf/compileRouteUrlFormater.test.ts b/packages/api/test/perf/compileRouteUrlFormater.test.ts index 5abdc2d03a30..ab16e1a5d14e 100644 --- a/packages/api/test/perf/compileRouteUrlFormater.test.ts +++ b/packages/api/test/perf/compileRouteUrlFormater.test.ts @@ -1,19 +1,19 @@ -import {compileRouteUrlFormater} from "../../src/utils/urlFormat.js"; +import {compileRouteUrlFormatter} from "../../src/utils/urlFormat.js"; /* eslint-disable no-console */ describe("route parse", () => { - it.skip("Benchmark compileRouteUrlFormater", () => { + it.skip("Benchmark compileRouteUrlFormatter", () => { const path = "/eth/v1/validator/:name/attester/:epoch"; const args = {epoch: 5, name: "HEAD"}; console.time("compile"); for (let i = 0; i < 1e6; i++) { - compileRouteUrlFormater(path); + compileRouteUrlFormatter(path); } console.timeEnd("compile"); - const fn = compileRouteUrlFormater(path); + const fn = compileRouteUrlFormatter(path); console.log(fn(args)); diff --git a/packages/api/test/unit/beacon/genericServerTest/beacon.test.ts b/packages/api/test/unit/beacon/genericServerTest/beacon.test.ts index 7972e4bfca65..a722e72a4c27 100644 --- a/packages/api/test/unit/beacon/genericServerTest/beacon.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/beacon.test.ts @@ -1,13 +1,13 @@ import {describe} from "vitest"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; -import {Api, ReqTypes} from "../../../../src/beacon/routes/beacon/index.js"; +import {Endpoints} from "../../../../src/beacon/routes/beacon/index.js"; import {getClient} from "../../../../src/beacon/client/beacon.js"; import {getRoutes} from "../../../../src/beacon/server/beacon.js"; import {runGenericServerTest} from "../../../utils/genericServerTest.js"; import {testData} from "../testData/beacon.js"; describe("beacon / beacon", () => { - runGenericServerTest( + runGenericServerTest( // eslint-disable-next-line @typescript-eslint/naming-convention createChainForkConfig({...defaultChainConfig, ALTAIR_FORK_EPOCH: 1, BELLATRIX_FORK_EPOCH: 2}), getClient, diff --git a/packages/api/test/unit/beacon/genericServerTest/config.test.ts b/packages/api/test/unit/beacon/genericServerTest/config.test.ts index 3e9c001bffbf..8b924cf07693 100644 --- a/packages/api/test/unit/beacon/genericServerTest/config.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/config.test.ts @@ -1,6 +1,6 @@ import {describe, it, expect} from "vitest"; import {config} from "@lodestar/config/default"; -import {Api, ReqTypes, getReturnTypes} from "../../../../src/beacon/routes/config.js"; +import {Endpoints, getDefinitions} from "../../../../src/beacon/routes/config.js"; import {getClient} from "../../../../src/beacon/client/config.js"; import {getRoutes} from "../../../../src/beacon/server/config.js"; import {runGenericServerTest} from "../../../utils/genericServerTest.js"; @@ -9,10 +9,10 @@ import {testData} from "../testData/config.js"; /* eslint-disable @typescript-eslint/naming-convention */ describe("beacon / config", () => { - runGenericServerTest(config, getClient, getRoutes, testData); + runGenericServerTest(config, getClient, getRoutes, testData); it("Serialize Partial Spec object", () => { - const returnTypes = getReturnTypes(); + const {getSpec} = getDefinitions(config); const partialJsonSpec: Record = { PRESET_BASE: "mainnet", @@ -22,9 +22,9 @@ describe("beacon / config", () => { MIN_GENESIS_TIME: "1606824000", }; - const jsonRes = returnTypes.getSpec.toJson({data: partialJsonSpec}); - const specRes = returnTypes.getSpec.fromJson(jsonRes); + const jsonRes = getSpec.resp.data.toJson(partialJsonSpec); + const specRes = getSpec.resp.data.fromJson(jsonRes); - expect(specRes).toEqual({data: partialJsonSpec}); + expect(specRes).toEqual(partialJsonSpec); }); }); diff --git a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts index 3a8ccd0afe25..54158cda392c 100644 --- a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts @@ -1,27 +1,30 @@ -import {describe, it, expect, MockInstance, beforeAll, afterAll, vi} from "vitest"; +import {describe, it, expect, beforeAll, afterAll, vi} from "vitest"; import {toHexString} from "@chainsafe/ssz"; import {FastifyInstance} from "fastify"; +import {ForkName} from "@lodestar/params"; import {ssz} from "@lodestar/types"; import {config} from "@lodestar/config/default"; -import {Api, ReqTypes, routesData} from "../../../../src/beacon/routes/debug.js"; +import {Endpoints, getDefinitions} from "../../../../src/beacon/routes/debug.js"; import {getClient} from "../../../../src/beacon/client/debug.js"; import {getRoutes} from "../../../../src/beacon/server/debug.js"; import {runGenericServerTest} from "../../../utils/genericServerTest.js"; import {getMockApi, getTestServer} from "../../../utils/utils.js"; -import {registerRoute} from "../../../../src/utils/server/registerRoute.js"; import {HttpClient} from "../../../../src/utils/client/httpClient.js"; import {testData} from "../testData/debug.js"; +import {FastifyRoute} from "../../../../src/utils/server/index.js"; +import {AnyEndpoint} from "../../../../src/utils/codecs.js"; +import {WireFormat} from "../../../../src/utils/wireFormat.js"; describe("beacon / debug", () => { // Extend timeout since states are very big vi.setConfig({testTimeout: 30_000}); - runGenericServerTest(config, getClient, getRoutes, testData); + runGenericServerTest(config, getClient, getRoutes, testData); // Get state by SSZ describe("getState() in SSZ format", () => { - const mockApi = getMockApi(routesData); + const mockApi = getMockApi(getDefinitions(config)); let baseUrl: string; let server: FastifyInstance; @@ -29,7 +32,7 @@ describe("beacon / debug", () => { const res = getTestServer(); server = res.server; for (const route of Object.values(getRoutes(config, mockApi))) { - registerRoute(server, route); + server.route(route as FastifyRoute); } baseUrl = await res.start(); }); @@ -42,17 +45,20 @@ describe("beacon / debug", () => { it(method, async () => { const state = ssz.phase0.BeaconState.defaultValue(); const stateSerialized = ssz.phase0.BeaconState.serialize(state); - (mockApi[method] as MockInstance).mockResolvedValue(stateSerialized); + mockApi[method].mockResolvedValue({ + data: stateSerialized, + meta: {version: ForkName.phase0, executionOptimistic: false, finalized: false}, + }); const httpClient = new HttpClient({baseUrl}); const client = getClient(config, httpClient); - const res = await client[method]("head", "ssz"); + const res = await client[method]({stateId: "head"}, {responseWireFormat: WireFormat.ssz}); expect(res.ok).toBe(true); if (res.ok) { - expect(toHexString(res.response)).toBe(toHexString(stateSerialized)); + expect(toHexString(res.ssz())).toBe(toHexString(stateSerialized)); } }); } diff --git a/packages/api/test/unit/beacon/genericServerTest/events.test.ts b/packages/api/test/unit/beacon/genericServerTest/events.test.ts index 2d6c9462c1fd..16123ec624e8 100644 --- a/packages/api/test/unit/beacon/genericServerTest/events.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/events.test.ts @@ -1,23 +1,23 @@ import {describe, it, expect, beforeEach, afterEach, beforeAll, afterAll} from "vitest"; import {FastifyInstance} from "fastify"; import {sleep} from "@lodestar/utils"; -import {Api, routesData, EventType, BeaconEvent} from "../../../../src/beacon/routes/events.js"; +import {config} from "@lodestar/config/default"; +import {Endpoints, getDefinitions, EventType, BeaconEvent} from "../../../../src/beacon/routes/events.js"; import {getClient} from "../../../../src/beacon/client/events.js"; import {getRoutes} from "../../../../src/beacon/server/events.js"; -import {registerRoute} from "../../../../src/utils/server/registerRoute.js"; import {getMockApi, getTestServer} from "../../../utils/utils.js"; import {eventTestData} from "../testData/events.js"; describe("beacon / events", () => { - const mockApi = getMockApi(routesData); + const mockApi = getMockApi(getDefinitions(config)); let server: FastifyInstance; let baseUrl: string; beforeAll(async () => { const res = getTestServer(); server = res.server; - for (const route of Object.values(getRoutes(mockApi))) { - registerRoute(server, route); + for (const route of Object.values(getRoutes(config, mockApi))) { + server.route(route); } baseUrl = await res.start(); @@ -52,7 +52,7 @@ describe("beacon / events", () => { const eventsReceived: BeaconEvent[] = []; await new Promise((resolve, reject) => { - mockApi.eventstream.mockImplementation(async (topics, signal, onEvent) => { + mockApi.eventstream.mockImplementation(async ({topics, onEvent}) => { try { expect(topics).toEqual(topicsToRequest); for (const event of eventsToSend) { @@ -60,15 +60,19 @@ describe("beacon / events", () => { await sleep(5); } } catch (e) { - reject(e as Error); + reject(e); } }); // Capture them on the client - const client = getClient(baseUrl); - void client.eventstream(topicsToRequest, controller.signal, (event) => { - eventsReceived.push(event); - if (eventsReceived.length >= eventsToSend.length) resolve(); + const client = getClient(config, baseUrl); + void client.eventstream({ + topics: topicsToRequest, + signal: controller.signal, + onEvent: (event) => { + eventsReceived.push(event); + if (eventsReceived.length >= eventsToSend.length) resolve(); + }, }); }); diff --git a/packages/api/test/unit/beacon/genericServerTest/lightclient.test.ts b/packages/api/test/unit/beacon/genericServerTest/lightclient.test.ts index 10031a150490..5bf8b7827161 100644 --- a/packages/api/test/unit/beacon/genericServerTest/lightclient.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/lightclient.test.ts @@ -1,11 +1,11 @@ import {describe} from "vitest"; import {config} from "@lodestar/config/default"; -import {Api, ReqTypes} from "../../../../src/beacon/routes/lightclient.js"; +import {Endpoints} from "../../../../src/beacon/routes/lightclient.js"; import {getClient} from "../../../../src/beacon/client/lightclient.js"; import {getRoutes} from "../../../../src/beacon/server/lightclient.js"; import {runGenericServerTest} from "../../../utils/genericServerTest.js"; import {testData} from "../testData/lightclient.js"; describe("beacon / lightclient", () => { - runGenericServerTest(config, getClient, getRoutes, testData); + runGenericServerTest(config, getClient, getRoutes, testData); }); diff --git a/packages/api/test/unit/beacon/genericServerTest/node.test.ts b/packages/api/test/unit/beacon/genericServerTest/node.test.ts index 059bd4ca2c88..0affaf014c4a 100644 --- a/packages/api/test/unit/beacon/genericServerTest/node.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/node.test.ts @@ -1,11 +1,11 @@ import {describe} from "vitest"; import {config} from "@lodestar/config/default"; -import {Api, ReqTypes} from "../../../../src/beacon/routes/node.js"; +import {Endpoints} from "../../../../src/beacon/routes/node.js"; import {getClient} from "../../../../src/beacon/client/node.js"; import {getRoutes} from "../../../../src/beacon/server/node.js"; import {runGenericServerTest} from "../../../utils/genericServerTest.js"; import {testData} from "../testData/node.js"; describe("beacon / node", () => { - runGenericServerTest(config, getClient, getRoutes, testData); + runGenericServerTest(config, getClient, getRoutes, testData); }); diff --git a/packages/api/test/unit/beacon/genericServerTest/proofs.test.ts b/packages/api/test/unit/beacon/genericServerTest/proofs.test.ts index 4619d20d989f..d31137886e2f 100644 --- a/packages/api/test/unit/beacon/genericServerTest/proofs.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/proofs.test.ts @@ -1,11 +1,11 @@ import {describe} from "vitest"; import {config} from "@lodestar/config/default"; -import {Api, ReqTypes} from "../../../../src/beacon/routes/proof.js"; +import {Endpoints} from "../../../../src/beacon/routes/proof.js"; import {getClient} from "../../../../src/beacon/client/proof.js"; import {getRoutes} from "../../../../src/beacon/server/proof.js"; import {runGenericServerTest} from "../../../utils/genericServerTest.js"; import {testData} from "../testData/proofs.js"; describe("beacon / proofs", () => { - runGenericServerTest(config, getClient, getRoutes, testData); + runGenericServerTest(config, getClient, getRoutes, testData); }); diff --git a/packages/api/test/unit/beacon/genericServerTest/validator.test.ts b/packages/api/test/unit/beacon/genericServerTest/validator.test.ts index 5a87ea9eee5f..0aed65cd6f17 100644 --- a/packages/api/test/unit/beacon/genericServerTest/validator.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/validator.test.ts @@ -1,13 +1,13 @@ import {describe} from "vitest"; import {config} from "@lodestar/config/default"; -import {Api, ReqTypes} from "../../../../src/beacon/routes/validator.js"; +import {Endpoints} from "../../../../src/beacon/routes/validator.js"; import {getClient} from "../../../../src/beacon/client/validator.js"; import {getRoutes} from "../../../../src/beacon/server/validator.js"; import {runGenericServerTest} from "../../../utils/genericServerTest.js"; import {testData} from "../testData/validator.js"; describe("beacon / validator", () => { - runGenericServerTest(config, getClient, getRoutes, testData); + runGenericServerTest(config, getClient, getRoutes, testData); // TODO: Extra tests to implement maybe diff --git a/packages/api/test/unit/beacon/oapiSpec.test.ts b/packages/api/test/unit/beacon/oapiSpec.test.ts index 1bffbb486ff7..6b238bf8cd36 100644 --- a/packages/api/test/unit/beacon/oapiSpec.test.ts +++ b/packages/api/test/unit/beacon/oapiSpec.test.ts @@ -4,8 +4,6 @@ import {describe, it, beforeAll, expect} from "vitest"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {OpenApiFile} from "../../utils/parseOpenApiSpec.js"; import {routes} from "../../../src/beacon/index.js"; -import {ReqSerializers} from "../../../src/utils/types.js"; -import {Schema} from "../../../src/utils/schema.js"; import {IgnoredProperty, runTestCheckAgainstSpec} from "../../utils/checkAgainstSpec.js"; import {fetchOpenApiSpec} from "../../utils/fetchOpenApiSpec.js"; // Import all testData and merge below @@ -30,47 +28,18 @@ const openApiFile: OpenApiFile = { version: RegExp(version), }; -const routesData = { - ...routes.beacon.routesData, - ...routes.config.routesData, - ...routes.debug.routesData, - ...routes.events.routesData, - ...routes.lightclient.routesData, - ...routes.node.routesData, - ...routes.proof.routesData, - ...routes.validator.routesData, -}; - -// Additional definition not used in production -const getEventsReqSerializers = (): ReqSerializers => ({ - eventstream: { - writeReq: (topics) => ({query: {topics}}), - parseReq: ({query}) => [query.topics, null as any, null as any], - schema: {query: {topics: Schema.StringArray}}, - }, -}); - // eslint-disable-next-line @typescript-eslint/naming-convention const config = createChainForkConfig({...defaultChainConfig, ALTAIR_FORK_EPOCH: 1, BELLATRIX_FORK_EPOCH: 2}); -const reqSerializers = { - ...routes.beacon.getReqSerializers(config), - ...routes.config.getReqSerializers(), - ...routes.debug.getReqSerializers(), - ...getEventsReqSerializers(), - ...routes.lightclient.getReqSerializers(), - ...routes.node.getReqSerializers(), - ...routes.proof.getReqSerializers(), - ...routes.validator.getReqSerializers(), -}; -const returnTypes = { - ...routes.beacon.getReturnTypes(), - ...routes.config.getReturnTypes(), - ...routes.debug.getReturnTypes(), - ...routes.lightclient.getReturnTypes(), - ...routes.node.getReturnTypes(), - ...routes.proof.getReturnTypes(), - ...routes.validator.getReturnTypes(), +const definitions = { + ...routes.beacon.getDefinitions(config), + ...routes.config.getDefinitions(config), + ...routes.debug.getDefinitions(config), + ...routes.events.getDefinitions(config), + ...routes.lightclient.getDefinitions(config), + ...routes.node.getDefinitions(config), + ...routes.proof.getDefinitions(config), + ...routes.validator.getDefinitions(config), }; const testDatas = { @@ -90,13 +59,8 @@ const ignoredOperations = [ "getBlindedBlock", // https://github.com/ChainSafe/lodestar/issues/5699 "getNextWithdrawals", // https://github.com/ChainSafe/lodestar/issues/5696 "getDebugForkChoice", // https://github.com/ChainSafe/lodestar/issues/5700 - /* Ensure operationId matches spec value, blocked by https://github.com/ChainSafe/lodestar/pull/6080 */ - "getLightClientBootstrap", - "getLightClientUpdatesByRange", - "getLightClientFinalityUpdate", - "getLightClientOptimisticUpdate", - "getPoolBLSToExecutionChanges", - "submitPoolBLSToExecutionChange", + /* Must support ssz response body */ + "getLightClientUpdatesByRange", // https://github.com/ChainSafe/lodestar/issues/6841 ]; const ignoredProperties: Record = { @@ -115,15 +79,7 @@ const ignoredProperties: Record = { }; const openApiJson = await fetchOpenApiSpec(openApiFile); -runTestCheckAgainstSpec( - openApiJson, - routesData, - reqSerializers, - returnTypes, - testDatas, - ignoredOperations, - ignoredProperties -); +runTestCheckAgainstSpec(openApiJson, definitions, testDatas, ignoredOperations, ignoredProperties); const ignoredTopics = [ /* @@ -145,7 +101,7 @@ describe("eventstream event data", () => { // "value": "event: head\ndata: {\"slot\":\"10\", \"block\":\"0x9a2fefd2fdb57f74993c7780ea5b9030d2897b615b89f808011ca5aebed54eaf\", \"state\":\"0x600e852a08c1200654ddf11025f1ceacb3c2e74bdd5c630cde0838b2591b69f9\", \"epoch_transition\":false, \"previous_duty_dependent_root\":\"0x5e0043f107cb57913498fbf2f99ff55e730bf1e151f02f221e977c91a90a0e91\", \"current_duty_dependent_root\":\"0x5e0043f107cb57913498fbf2f99ff55e730bf1e151f02f221e977c91a90a0e91\", \"execution_optimistic\": false}\n" // }, ... } const eventstreamExamples = - openApiJson.paths["/eth/v1/events"]["get"].responses["200"].content?.["text/event-stream"].examples; + openApiJson.paths["/eth/v1/events"]["get"].responses["200"]?.content?.["text/event-stream"].examples; beforeAll(() => { if (!eventstreamExamples) { diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index 82e2ae1af421..9dad5f0079a2 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -1,10 +1,10 @@ import {toHexString} from "@chainsafe/ssz"; import {ForkName} from "@lodestar/params"; -import {ssz, Slot, allForks} from "@lodestar/types"; +import {Slot, allForks, ssz} from "@lodestar/types"; import { - Api, BlockHeaderResponse, BroadcastValidation, + Endpoints, ValidatorResponse, } from "../../../../src/beacon/routes/beacon/index.js"; import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; @@ -28,168 +28,172 @@ const validatorResponse: ValidatorResponse = { validator: ssz.phase0.Validator.defaultValue(), }; -export const testData: GenericServerTestCases = { +export const testData: GenericServerTestCases = { // block getBlock: { - args: ["head", "json"], + args: {blockId: "head"}, res: {data: ssz.phase0.SignedBeaconBlock.defaultValue()}, }, getBlockV2: { - args: ["head", "json"], + args: {blockId: "head"}, res: { - executionOptimistic: true, - finalized: false, data: ssz.bellatrix.SignedBeaconBlock.defaultValue(), - version: ForkName.bellatrix, + meta: {executionOptimistic: true, finalized: false, version: ForkName.bellatrix}, }, }, getBlockAttestations: { - args: ["head"], - res: {executionOptimistic: true, finalized: false, data: [ssz.phase0.Attestation.defaultValue()]}, + args: {blockId: "head"}, + res: {data: [ssz.phase0.Attestation.defaultValue()], meta: {executionOptimistic: true, finalized: false}}, }, getBlockHeader: { - args: ["head"], - res: {executionOptimistic: true, finalized: false, data: blockHeaderResponse}, + args: {blockId: "head"}, + res: {data: blockHeaderResponse, meta: {executionOptimistic: true, finalized: false}}, }, getBlockHeaders: { - args: [{slot: 1, parentRoot: toHexString(root)}], - res: {executionOptimistic: true, finalized: false, data: [blockHeaderResponse]}, + args: {slot: 1, parentRoot: toHexString(root)}, + res: {data: [blockHeaderResponse], meta: {executionOptimistic: true, finalized: false}}, }, getBlockRoot: { - args: ["head"], - res: {executionOptimistic: true, finalized: false, data: {root}}, + args: {blockId: "head"}, + res: {data: {root}, meta: {executionOptimistic: true, finalized: false}}, }, publishBlock: { - args: [ssz.phase0.SignedBeaconBlock.defaultValue()], + args: {signedBlockOrContents: ssz.phase0.SignedBeaconBlock.defaultValue()}, res: undefined, }, publishBlockV2: { - args: [ssz.phase0.SignedBeaconBlock.defaultValue(), {broadcastValidation: BroadcastValidation.consensus}], + args: { + signedBlockOrContents: ssz.phase0.SignedBeaconBlock.defaultValue(), + broadcastValidation: BroadcastValidation.consensus, + }, res: undefined, }, publishBlindedBlock: { - args: [getDefaultBlindedBlock(64)], + args: {signedBlindedBlock: getDefaultBlindedBlock(64)}, res: undefined, }, publishBlindedBlockV2: { - args: [getDefaultBlindedBlock(64), {broadcastValidation: BroadcastValidation.consensus}], + args: {signedBlindedBlock: getDefaultBlindedBlock(64), broadcastValidation: BroadcastValidation.consensus}, res: undefined, }, getBlobSidecars: { - args: ["head", [0]], - res: {executionOptimistic: true, finalized: false, data: ssz.deneb.BlobSidecars.defaultValue()}, + args: {blockId: "head", indices: [0]}, + res: { + data: [ssz.deneb.BlobSidecar.defaultValue()], + meta: {executionOptimistic: true, finalized: false, version: ForkName.deneb}, + }, }, // pool getPoolAttestations: { - args: [{slot: 1, committeeIndex: 2}], + args: {slot: 1, committeeIndex: 2}, res: {data: [ssz.phase0.Attestation.defaultValue()]}, }, getPoolAttesterSlashings: { - args: [], + args: undefined, res: {data: [ssz.phase0.AttesterSlashing.defaultValue()]}, }, getPoolProposerSlashings: { - args: [], + args: undefined, res: {data: [ssz.phase0.ProposerSlashing.defaultValue()]}, }, getPoolVoluntaryExits: { - args: [], + args: undefined, res: {data: [ssz.phase0.SignedVoluntaryExit.defaultValue()]}, }, - getPoolBlsToExecutionChanges: { - args: [], + getPoolBLSToExecutionChanges: { + args: undefined, res: {data: [ssz.capella.SignedBLSToExecutionChange.defaultValue()]}, }, submitPoolAttestations: { - args: [[ssz.phase0.Attestation.defaultValue()]], + args: {signedAttestations: [ssz.phase0.Attestation.defaultValue()]}, res: undefined, }, submitPoolAttesterSlashings: { - args: [ssz.phase0.AttesterSlashing.defaultValue()], + args: {attesterSlashing: ssz.phase0.AttesterSlashing.defaultValue()}, res: undefined, }, submitPoolProposerSlashings: { - args: [ssz.phase0.ProposerSlashing.defaultValue()], + args: {proposerSlashing: ssz.phase0.ProposerSlashing.defaultValue()}, res: undefined, }, submitPoolVoluntaryExit: { - args: [ssz.phase0.SignedVoluntaryExit.defaultValue()], + args: {signedVoluntaryExit: ssz.phase0.SignedVoluntaryExit.defaultValue()}, res: undefined, }, - submitPoolBlsToExecutionChange: { - args: [[ssz.capella.SignedBLSToExecutionChange.defaultValue()]], + submitPoolBLSToExecutionChange: { + args: {blsToExecutionChanges: [ssz.capella.SignedBLSToExecutionChange.defaultValue()]}, res: undefined, }, submitPoolSyncCommitteeSignatures: { - args: [[ssz.altair.SyncCommitteeMessage.defaultValue()]], + args: {signatures: [ssz.altair.SyncCommitteeMessage.defaultValue()]}, res: undefined, }, // state getStateRoot: { - args: ["head"], - res: {executionOptimistic: true, finalized: false, data: {root}}, + args: {stateId: "head"}, + res: {data: {root}, meta: {executionOptimistic: true, finalized: false}}, }, getStateFork: { - args: ["head"], - res: {executionOptimistic: true, finalized: false, data: ssz.phase0.Fork.defaultValue()}, + args: {stateId: "head"}, + res: {data: ssz.phase0.Fork.defaultValue(), meta: {executionOptimistic: true, finalized: false}}, }, getStateRandao: { - args: ["head", 1], - res: {executionOptimistic: true, finalized: false, data: {randao}}, + args: {stateId: "head", epoch: 1}, + res: {data: {randao}, meta: {executionOptimistic: true, finalized: false}}, }, getStateFinalityCheckpoints: { - args: ["head"], + args: {stateId: "head"}, res: { - executionOptimistic: true, - finalized: false, data: { previousJustified: ssz.phase0.Checkpoint.defaultValue(), currentJustified: ssz.phase0.Checkpoint.defaultValue(), finalized: ssz.phase0.Checkpoint.defaultValue(), }, + meta: {executionOptimistic: true, finalized: false}, }, }, getStateValidators: { - args: ["head", {id: [pubkeyHex, "1300"], status: ["active_ongoing"]}], - res: {executionOptimistic: true, finalized: false, data: [validatorResponse]}, + args: {stateId: "head", validatorIds: [pubkeyHex, "1300"], statuses: ["active_ongoing"]}, + res: {data: [validatorResponse], meta: {executionOptimistic: true, finalized: false}}, }, postStateValidators: { - args: ["head", {id: [pubkeyHex, 1300], status: ["active_ongoing"]}], - res: {executionOptimistic: true, finalized: false, data: [validatorResponse]}, + args: {stateId: "head", validatorIds: [pubkeyHex, 1300], statuses: ["active_ongoing"]}, + res: {data: [validatorResponse], meta: {executionOptimistic: true, finalized: false}}, }, getStateValidator: { - args: ["head", pubkeyHex], - res: {executionOptimistic: true, finalized: false, data: validatorResponse}, + args: {stateId: "head", validatorId: pubkeyHex}, + res: {data: validatorResponse, meta: {executionOptimistic: true, finalized: false}}, }, getStateValidatorBalances: { - args: ["head", ["1300"]], - res: {executionOptimistic: true, finalized: false, data: [{index: 1300, balance}]}, + args: {stateId: "head", validatorIds: ["1300"]}, + res: {data: [{index: 1300, balance}], meta: {executionOptimistic: true, finalized: false}}, }, postStateValidatorBalances: { - args: ["head", [1300]], - res: {executionOptimistic: true, finalized: false, data: [{index: 1300, balance}]}, + args: {stateId: "head", validatorIds: [1300]}, + res: {data: [{index: 1300, balance}], meta: {executionOptimistic: true, finalized: false}}, }, getEpochCommittees: { - args: ["head", {index: 1, slot: 2, epoch: 3}], - res: {executionOptimistic: true, finalized: false, data: [{index: 1, slot: 2, validators: [1300]}]}, + args: {stateId: "head", index: 1, slot: 2, epoch: 3}, + res: {data: [{index: 1, slot: 2, validators: [1300]}], meta: {executionOptimistic: true, finalized: false}}, }, getEpochSyncCommittees: { - args: ["head", 1], - res: {executionOptimistic: true, finalized: false, data: {validators: [1300], validatorAggregates: [[1300]]}}, + args: {stateId: "head", epoch: 1}, + res: { + data: {validators: [1300], validatorAggregates: [[1300]]}, + meta: {executionOptimistic: true, finalized: false}, + }, }, - // reward + // rewards getBlockRewards: { - args: ["head"], + args: {blockId: "head"}, res: { - executionOptimistic: true, - finalized: false, data: { proposerIndex: 0, total: 15, @@ -198,18 +202,12 @@ export const testData: GenericServerTestCases = { proposerSlashings: 2, attesterSlashings: 1, }, + meta: {executionOptimistic: true, finalized: false}, }, }, - getSyncCommitteeRewards: { - args: ["head", ["1300"]], - res: {executionOptimistic: true, finalized: false, data: [{validatorIndex: 1300, reward}]}, - }, - getAttestationsRewards: { - args: [10, ["1300"]], + args: {epoch: 10, validatorIds: [1300]}, res: { - executionOptimistic: true, - finalized: false, data: { idealRewards: [ { @@ -232,13 +230,18 @@ export const testData: GenericServerTestCases = { }, ], }, + meta: {executionOptimistic: true, finalized: false}, }, }, + getSyncCommitteeRewards: { + args: {blockId: "head", validatorIds: [1300]}, + res: {data: [{validatorIndex: 1300, reward}], meta: {executionOptimistic: true, finalized: false}}, + }, // - getGenesis: { - args: [], + args: undefined, res: {data: ssz.phase0.Genesis.defaultValue()}, }, }; diff --git a/packages/api/test/unit/beacon/testData/config.ts b/packages/api/test/unit/beacon/testData/config.ts index 642ed5e7e224..dddc90ae6b25 100644 --- a/packages/api/test/unit/beacon/testData/config.ts +++ b/packages/api/test/unit/beacon/testData/config.ts @@ -2,16 +2,16 @@ import {ssz} from "@lodestar/types"; import {chainConfigToJson} from "@lodestar/config"; import {chainConfig} from "@lodestar/config/default"; import {activePreset, presetToJson} from "@lodestar/params"; -import {Api} from "../../../../src/beacon/routes/config.js"; +import {Endpoints} from "../../../../src/beacon/routes/config.js"; import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; const configJson = chainConfigToJson(chainConfig); const presetJson = presetToJson(activePreset); const jsonSpec = {...configJson, ...presetJson}; -export const testData: GenericServerTestCases = { +export const testData: GenericServerTestCases = { getDepositContract: { - args: [], + args: undefined, res: { data: { chainId: 1, @@ -20,11 +20,11 @@ export const testData: GenericServerTestCases = { }, }, getForkSchedule: { - args: [], + args: undefined, res: {data: [ssz.phase0.Fork.defaultValue()]}, }, getSpec: { - args: [], + args: undefined, res: {data: jsonSpec}, }, }; diff --git a/packages/api/test/unit/beacon/testData/debug.ts b/packages/api/test/unit/beacon/testData/debug.ts index aa595046b8ba..386443c76f3d 100644 --- a/packages/api/test/unit/beacon/testData/debug.ts +++ b/packages/api/test/unit/beacon/testData/debug.ts @@ -1,22 +1,22 @@ import {toHexString} from "@chainsafe/ssz"; import {ForkName} from "@lodestar/params"; import {ssz} from "@lodestar/types"; -import {Api} from "../../../../src/beacon/routes/debug.js"; +import {Endpoints} from "../../../../src/beacon/routes/debug.js"; import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; const rootHex = toHexString(Buffer.alloc(32, 1)); -export const testData: GenericServerTestCases = { +export const testData: GenericServerTestCases = { getDebugChainHeads: { - args: [], + args: undefined, res: {data: [{slot: 1, root: rootHex}]}, }, getDebugChainHeadsV2: { - args: [], + args: undefined, res: {data: [{slot: 1, root: rootHex, executionOptimistic: true}]}, }, getProtoArrayNodes: { - args: [], + args: undefined, res: { data: [ { @@ -46,16 +46,14 @@ export const testData: GenericServerTestCases = { }, }, getState: { - args: ["head", "json"], - res: {executionOptimistic: true, finalized: false, data: ssz.phase0.BeaconState.defaultValue()}, + args: {stateId: "head"}, + res: {data: ssz.phase0.BeaconState.defaultValue(), meta: {executionOptimistic: true, finalized: false}}, }, getStateV2: { - args: ["head", "json"], + args: {stateId: "head"}, res: { - executionOptimistic: true, - finalized: false, data: ssz.altair.BeaconState.defaultValue(), - version: ForkName.altair, + meta: {executionOptimistic: true, finalized: false, version: ForkName.altair}, }, }, }; diff --git a/packages/api/test/unit/beacon/testData/events.ts b/packages/api/test/unit/beacon/testData/events.ts index 1ac101f32f4d..8a7610a26836 100644 --- a/packages/api/test/unit/beacon/testData/events.ts +++ b/packages/api/test/unit/beacon/testData/events.ts @@ -1,15 +1,15 @@ import {ssz} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; -import {Api, EventData, EventType, blobSidecarSSE} from "../../../../src/beacon/routes/events.js"; +import {Endpoints, EventData, EventType, blobSidecarSSE} from "../../../../src/beacon/routes/events.js"; import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; const abortController = new AbortController(); /* eslint-disable @typescript-eslint/naming-convention */ -export const testData: GenericServerTestCases = { +export const testData: GenericServerTestCases = { eventstream: { - args: [[EventType.head, EventType.chainReorg], abortController.signal, function onEvent() {}], + args: {topics: [EventType.head, EventType.chainReorg], signal: abortController.signal, onEvent: () => {}}, res: undefined, }, }; diff --git a/packages/api/test/unit/beacon/testData/lightclient.ts b/packages/api/test/unit/beacon/testData/lightclient.ts index 13e08e365987..b86e1f338329 100644 --- a/packages/api/test/unit/beacon/testData/lightclient.ts +++ b/packages/api/test/unit/beacon/testData/lightclient.ts @@ -1,29 +1,28 @@ import {toHexString} from "@chainsafe/ssz"; import {ssz} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; -import {Api} from "../../../../src/beacon/routes/lightclient.js"; +import {Endpoints} from "../../../../src/beacon/routes/lightclient.js"; import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; -const root = Uint8Array.from(Buffer.alloc(32, 1)); +const root = new Uint8Array(32).fill(1); const lightClientUpdate = ssz.altair.LightClientUpdate.defaultValue(); const syncAggregate = ssz.altair.SyncAggregate.defaultValue(); const header = ssz.altair.LightClientHeader.defaultValue(); const signatureSlot = ssz.Slot.defaultValue(); -export const testData: GenericServerTestCases = { - getUpdates: { - args: [1, 2], - res: [{version: ForkName.bellatrix, data: lightClientUpdate}], +export const testData: GenericServerTestCases = { + getLightClientUpdatesByRange: { + args: {startPeriod: 1, count: 2}, + res: {data: [lightClientUpdate], meta: {versions: [ForkName.bellatrix]}}, }, - getOptimisticUpdate: { - args: [], - res: {version: ForkName.bellatrix, data: {syncAggregate, attestedHeader: header, signatureSlot}}, + getLightClientOptimisticUpdate: { + args: undefined, + res: {data: {syncAggregate, attestedHeader: header, signatureSlot}, meta: {version: ForkName.bellatrix}}, }, - getFinalityUpdate: { - args: [], + getLightClientFinalityUpdate: { + args: undefined, res: { - version: ForkName.bellatrix, data: { syncAggregate, attestedHeader: header, @@ -31,21 +30,22 @@ export const testData: GenericServerTestCases = { finalityBranch: lightClientUpdate.finalityBranch, signatureSlot: lightClientUpdate.attestedHeader.beacon.slot + 1, }, + meta: {version: ForkName.bellatrix}, }, }, - getBootstrap: { - args: [toHexString(root)], + getLightClientBootstrap: { + args: {blockRoot: toHexString(root)}, res: { - version: ForkName.bellatrix, data: { header, currentSyncCommittee: lightClientUpdate.nextSyncCommittee, currentSyncCommitteeBranch: [root, root, root, root, root], // Vector(Root, 5) }, + meta: {version: ForkName.bellatrix}, }, }, - getCommitteeRoot: { - args: [1, 2], - res: {data: [Uint8Array.from(Buffer.alloc(32, 0)), Uint8Array.from(Buffer.alloc(32, 1))]}, + getLightClientCommitteeRoot: { + args: {startPeriod: 1, count: 2}, + res: {data: [new Uint8Array(32), new Uint8Array(32).fill(1)]}, }, }; diff --git a/packages/api/test/unit/beacon/testData/node.ts b/packages/api/test/unit/beacon/testData/node.ts index 2243f37fc4c7..48efc4a728bc 100644 --- a/packages/api/test/unit/beacon/testData/node.ts +++ b/packages/api/test/unit/beacon/testData/node.ts @@ -1,5 +1,5 @@ import {ssz} from "@lodestar/types"; -import {Api, NodePeer} from "../../../../src/beacon/routes/node.js"; +import {Endpoints, NodePeer} from "../../../../src/beacon/routes/node.js"; import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; const peerIdStr = "peerId"; @@ -11,9 +11,9 @@ const nodePeer: NodePeer = { direction: "inbound", }; -export const testData: GenericServerTestCases = { +export const testData: GenericServerTestCases = { getNetworkIdentity: { - args: [], + args: undefined, res: { data: { peerId: peerIdStr, @@ -25,15 +25,15 @@ export const testData: GenericServerTestCases = { }, }, getPeers: { - args: [{state: ["connected", "disconnected"], direction: ["inbound"]}], + args: {state: ["connected", "disconnected"], direction: ["inbound"]}, res: {data: [nodePeer], meta: {count: 1}}, }, getPeer: { - args: [peerIdStr], + args: {peerId: peerIdStr}, res: {data: nodePeer}, }, getPeerCount: { - args: [], + args: undefined, res: { data: { disconnected: 1, @@ -44,15 +44,15 @@ export const testData: GenericServerTestCases = { }, }, getNodeVersion: { - args: [], + args: undefined, res: {data: {version: "Lodestar/v0.20.0"}}, }, getSyncingStatus: { - args: [], + args: undefined, res: {data: {headSlot: "1", syncDistance: "2", isSyncing: false, isOptimistic: true, elOffline: false}}, }, getHealth: { - args: [{syncingStatus: 206}], + args: {syncingStatus: 206}, res: undefined, }, }; diff --git a/packages/api/test/unit/beacon/testData/proofs.ts b/packages/api/test/unit/beacon/testData/proofs.ts index 3af25fe7b5d0..2e24d1c18a71 100644 --- a/packages/api/test/unit/beacon/testData/proofs.ts +++ b/packages/api/test/unit/beacon/testData/proofs.ts @@ -1,35 +1,32 @@ import {ProofType} from "@chainsafe/persistent-merkle-tree"; -import {Api} from "../../../../src/beacon/routes/proof.js"; +import {ForkName} from "@lodestar/params"; +import {Endpoints} from "../../../../src/beacon/routes/proof.js"; import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; -const root = Uint8Array.from(Buffer.alloc(32, 1)); +const root = new Uint8Array(32).fill(1); const descriptor = Uint8Array.from([0, 0, 0, 0]); -export const testData: GenericServerTestCases = { +export const testData: GenericServerTestCases = { getStateProof: { - args: ["head", descriptor], + args: {stateId: "head", descriptor}, res: { data: { type: ProofType.compactMulti, descriptor, leaves: [root, root, root, root], }, - }, - query: { - format: "0x00000000", + meta: {version: ForkName.altair}, }, }, getBlockProof: { - args: ["head", descriptor], + args: {blockId: "head", descriptor}, res: { data: { type: ProofType.compactMulti, descriptor, leaves: [root, root, root, root], }, - }, - query: { - format: "0x00000000", + meta: {version: ForkName.altair}, }, }, }; diff --git a/packages/api/test/unit/beacon/testData/validator.ts b/packages/api/test/unit/beacon/testData/validator.ts index 6b410b724e4f..3a92beb7ad27 100644 --- a/packages/api/test/unit/beacon/testData/validator.ts +++ b/packages/api/test/unit/beacon/testData/validator.ts @@ -1,6 +1,6 @@ import {ForkName} from "@lodestar/params"; import {ssz, ProducedBlockSource} from "@lodestar/types"; -import {Api} from "../../../../src/beacon/routes/validator.js"; +import {BuilderSelection, Endpoints} from "../../../../src/beacon/routes/validator.js"; import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; const ZERO_HASH = new Uint8Array(32); @@ -10,11 +10,10 @@ const selectionProof = new Uint8Array(96).fill(1); const graffiti = "a".repeat(32); const feeRecipient = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; -export const testData: GenericServerTestCases = { +export const testData: GenericServerTestCases = { getAttesterDuties: { - args: [1000, [1, 2, 3]], + args: {epoch: 1000, indices: [1, 2, 3]}, res: { - executionOptimistic: true, data: [ { pubkey: new Uint8Array(48).fill(1), @@ -26,151 +25,121 @@ export const testData: GenericServerTestCases = { slot: 7, }, ], - dependentRoot: ZERO_HASH_HEX, + meta: {executionOptimistic: true, dependentRoot: ZERO_HASH_HEX}, }, }, getProposerDuties: { - args: [1000], + args: {epoch: 1000}, res: { - executionOptimistic: true, data: [{slot: 1, validatorIndex: 2, pubkey: new Uint8Array(48).fill(3)}], - dependentRoot: ZERO_HASH_HEX, + meta: {executionOptimistic: true, dependentRoot: ZERO_HASH_HEX}, }, }, getSyncCommitteeDuties: { - args: [1000, [1, 2, 3]], + args: {epoch: 1000, indices: [1, 2, 3]}, res: { - executionOptimistic: true, - data: [{pubkey: Uint8Array.from(Buffer.alloc(48, 1)), validatorIndex: 2, validatorSyncCommitteeIndices: [3]}], + data: [{pubkey: new Uint8Array(48).fill(1), validatorIndex: 2, validatorSyncCommitteeIndices: [3]}], + meta: {executionOptimistic: true}, }, }, produceBlock: { - args: [ - 32000, - randaoReveal, - graffiti, - false, - { - feeRecipient, - builderSelection: undefined, - strictFeeRecipientCheck: undefined, - blindedLocal: undefined, - builderBoostFactor: 100n, - }, - ] as unknown as GenericServerTestCases["produceBlock"]["args"], - res: {data: ssz.phase0.BeaconBlock.defaultValue()}, + args: {slot: 32000, randaoReveal, graffiti}, + res: {data: ssz.phase0.BeaconBlock.defaultValue(), meta: {version: ForkName.phase0}}, }, produceBlockV2: { - args: [ - 32000, + args: { + slot: 32000, randaoReveal, graffiti, - false, - { - feeRecipient, - builderSelection: undefined, - strictFeeRecipientCheck: undefined, - blindedLocal: undefined, - builderBoostFactor: 100n, - }, - ] as unknown as GenericServerTestCases["produceBlockV2"]["args"], + feeRecipient, + builderSelection: BuilderSelection.ExecutionAlways, + strictFeeRecipientCheck: true, + }, res: { data: ssz.altair.BeaconBlock.defaultValue(), - version: ForkName.altair, - executionPayloadValue: ssz.Wei.defaultValue(), - consensusBlockValue: ssz.Wei.defaultValue(), + meta: { + version: ForkName.altair, + }, }, }, produceBlockV3: { - args: [ - 32000, + args: { + slot: 32000, randaoReveal, graffiti, - true, - { - feeRecipient, - builderSelection: undefined, - strictFeeRecipientCheck: undefined, - blindedLocal: undefined, - builderBoostFactor: 100n, - }, - ], + skipRandaoVerification: true, + builderBoostFactor: 0n, + feeRecipient, + builderSelection: BuilderSelection.ExecutionAlways, + strictFeeRecipientCheck: true, + blindedLocal: false, + }, res: { data: ssz.altair.BeaconBlock.defaultValue(), - version: ForkName.altair, - executionPayloadValue: ssz.Wei.defaultValue(), - consensusBlockValue: ssz.Wei.defaultValue(), - executionPayloadBlinded: false, - executionPayloadSource: ProducedBlockSource.engine, + meta: { + version: ForkName.altair, + executionPayloadValue: ssz.Wei.defaultValue(), + consensusBlockValue: ssz.Wei.defaultValue(), + executionPayloadBlinded: false, + executionPayloadSource: ProducedBlockSource.engine, + }, }, }, produceBlindedBlock: { - args: [ - 32000, - randaoReveal, - graffiti, - false, - { - feeRecipient, - builderSelection: undefined, - strictFeeRecipientCheck: undefined, - blindedLocal: undefined, - builderBoostFactor: 100n, - }, - ] as unknown as GenericServerTestCases["produceBlindedBlock"]["args"], + args: {slot: 32000, randaoReveal, graffiti}, res: { data: ssz.bellatrix.BlindedBeaconBlock.defaultValue(), - version: ForkName.bellatrix, - executionPayloadValue: ssz.Wei.defaultValue(), - consensusBlockValue: ssz.Wei.defaultValue(), + meta: { + version: ForkName.bellatrix, + }, }, }, produceAttestationData: { - args: [2, 32000], + args: {committeeIndex: 2, slot: 32000}, res: {data: ssz.phase0.AttestationData.defaultValue()}, }, produceSyncCommitteeContribution: { - args: [32000, 2, ZERO_HASH], + args: {slot: 32000, subcommitteeIndex: 2, beaconBlockRoot: ZERO_HASH}, res: {data: ssz.altair.SyncCommitteeContribution.defaultValue()}, }, getAggregatedAttestation: { - args: [ZERO_HASH, 32000], + args: {attestationDataRoot: ZERO_HASH, slot: 32000}, res: {data: ssz.phase0.Attestation.defaultValue()}, }, publishAggregateAndProofs: { - args: [[ssz.phase0.SignedAggregateAndProof.defaultValue()]], + args: {signedAggregateAndProofs: [ssz.phase0.SignedAggregateAndProof.defaultValue()]}, res: undefined, }, publishContributionAndProofs: { - args: [[ssz.altair.SignedContributionAndProof.defaultValue()]], + args: {contributionAndProofs: [ssz.altair.SignedContributionAndProof.defaultValue()]}, res: undefined, }, prepareBeaconCommitteeSubnet: { - args: [[{validatorIndex: 1, committeeIndex: 2, committeesAtSlot: 3, slot: 4, isAggregator: true}]], + args: {subscriptions: [{validatorIndex: 1, committeeIndex: 2, committeesAtSlot: 3, slot: 4, isAggregator: true}]}, res: undefined, }, prepareSyncCommitteeSubnets: { - args: [[{validatorIndex: 1, syncCommitteeIndices: [2], untilEpoch: 3}]], + args: {subscriptions: [{validatorIndex: 1, syncCommitteeIndices: [2], untilEpoch: 3}]}, res: undefined, }, prepareBeaconProposer: { - args: [[{validatorIndex: "1", feeRecipient: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"}]], + args: {proposers: [{validatorIndex: 1, feeRecipient: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"}]}, res: undefined, }, submitBeaconCommitteeSelections: { - args: [[]], + args: {selections: []}, res: {data: [{validatorIndex: 1, slot: 2, selectionProof}]}, }, submitSyncCommitteeSelections: { - args: [[]], + args: {selections: []}, res: {data: [{validatorIndex: 1, slot: 2, subcommitteeIndex: 3, selectionProof}]}, }, getLiveness: { - args: [0, [0]], + args: {epoch: 0, indices: [0]}, res: {data: []}, }, registerValidator: { - args: [[ssz.bellatrix.SignedValidatorRegistrationV1.defaultValue()]], + args: {registrations: [ssz.bellatrix.SignedValidatorRegistrationV1.defaultValue()]}, res: undefined, }, }; diff --git a/packages/api/test/unit/builder/builder.test.ts b/packages/api/test/unit/builder/builder.test.ts index 56b8eee45ea5..045a66496b6f 100644 --- a/packages/api/test/unit/builder/builder.test.ts +++ b/packages/api/test/unit/builder/builder.test.ts @@ -1,13 +1,13 @@ import {describe} from "vitest"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; -import {Api, ReqTypes} from "../../../src/builder/routes.js"; +import {Endpoints} from "../../../src/builder/routes.js"; import {getClient} from "../../../src/builder/client.js"; import {getRoutes} from "../../../src/builder/server/index.js"; import {runGenericServerTest} from "../../utils/genericServerTest.js"; import {testData} from "./testData.js"; describe("builder", () => { - runGenericServerTest( + runGenericServerTest( createChainForkConfig({ ...defaultChainConfig, /* eslint-disable @typescript-eslint/naming-convention */ diff --git a/packages/api/test/unit/builder/oapiSpec.test.ts b/packages/api/test/unit/builder/oapiSpec.test.ts index 10fb1c62085f..60168f71d8d8 100644 --- a/packages/api/test/unit/builder/oapiSpec.test.ts +++ b/packages/api/test/unit/builder/oapiSpec.test.ts @@ -3,7 +3,7 @@ import {fileURLToPath} from "node:url"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {OpenApiFile} from "../../utils/parseOpenApiSpec.js"; -import {routesData, getReqSerializers, getReturnTypes} from "../../../src/builder/routes.js"; +import {getDefinitions} from "../../../src/builder/routes.js"; import {runTestCheckAgainstSpec} from "../../utils/checkAgainstSpec.js"; import {fetchOpenApiSpec} from "../../utils/fetchOpenApiSpec.js"; import {testData} from "./testData.js"; @@ -22,11 +22,10 @@ const openApiFile: OpenApiFile = { version: RegExp(/.*/), }; -const reqSerializers = getReqSerializers( +const definitions = getDefinitions( // eslint-disable-next-line @typescript-eslint/naming-convention createChainForkConfig({...defaultChainConfig, ALTAIR_FORK_EPOCH: 0, BELLATRIX_FORK_EPOCH: 0}) ); -const returnTypes = getReturnTypes(); const openApiJson = await fetchOpenApiSpec(openApiFile); -runTestCheckAgainstSpec(openApiJson, routesData, reqSerializers, returnTypes, testData); +runTestCheckAgainstSpec(openApiJson, definitions, testData); diff --git a/packages/api/test/unit/builder/testData.ts b/packages/api/test/unit/builder/testData.ts index e198e6971905..a23823702b6d 100644 --- a/packages/api/test/unit/builder/testData.ts +++ b/packages/api/test/unit/builder/testData.ts @@ -2,28 +2,28 @@ import {fromHexString} from "@chainsafe/ssz"; import {ssz} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; -import {Api} from "../../../src/builder/routes.js"; +import {Endpoints} from "../../../src/builder/routes.js"; import {GenericServerTestCases} from "../../utils/genericServerTest.js"; // randomly pregenerated pubkey const pubkeyRand = "0x84105a985058fc8740a48bf1ede9d223ef09e8c6b1735ba0a55cf4a9ff2ff92376b778798365e488dab07a652eb04576"; const root = new Uint8Array(32).fill(1); -export const testData: GenericServerTestCases = { +export const testData: GenericServerTestCases = { status: { - args: [], + args: undefined, res: undefined, }, registerValidator: { - args: [[ssz.bellatrix.SignedValidatorRegistrationV1.defaultValue()]], + args: {registrations: [ssz.bellatrix.SignedValidatorRegistrationV1.defaultValue()]}, res: undefined, }, getHeader: { - args: [1, root, fromHexString(pubkeyRand)], - res: {version: ForkName.bellatrix, data: ssz.bellatrix.SignedBuilderBid.defaultValue()}, + args: {slot: 1, parentHash: root, proposerPubkey: fromHexString(pubkeyRand)}, + res: {data: ssz.bellatrix.SignedBuilderBid.defaultValue(), meta: {version: ForkName.bellatrix}}, }, submitBlindedBlock: { - args: [ssz.deneb.SignedBlindedBeaconBlock.defaultValue()], - res: {version: ForkName.bellatrix, data: ssz.bellatrix.ExecutionPayload.defaultValue()}, + args: {signedBlindedBlock: ssz.deneb.SignedBlindedBeaconBlock.defaultValue()}, + res: {data: ssz.bellatrix.ExecutionPayload.defaultValue(), meta: {version: ForkName.bellatrix}}, }, }; diff --git a/packages/api/test/unit/client/httpClient.test.ts b/packages/api/test/unit/client/httpClient.test.ts index c82b879bdcf5..577b860c4cca 100644 --- a/packages/api/test/unit/client/httpClient.test.ts +++ b/packages/api/test/unit/client/httpClient.test.ts @@ -1,17 +1,29 @@ import {IncomingMessage} from "node:http"; -import {describe, it, afterEach, expect} from "vitest"; +import {describe, it, afterEach, expect, vi} from "vitest"; import {RouteOptions, fastify} from "fastify"; +import {BooleanType, ContainerType, UintNumberType, ValueOf} from "@chainsafe/ssz"; import {ErrorAborted, TimeoutError, toBase64} from "@lodestar/utils"; -import {HttpClient, HttpError} from "../../../src/utils/client/index.js"; -import {HttpStatusCode} from "../../../src/utils/client/httpStatusCode.js"; +import {HttpClient, RouteDefinitionExtra} from "../../../src/utils/client/index.js"; +import {HttpStatusCode} from "../../../src/utils/httpStatusCode.js"; +import { + AnyEndpoint, + EmptyArgs, + EmptyRequestCodec, + EmptyMeta, + EmptyRequest, + EmptyResponseCodec, + JsonOnlyReq, + JsonOnlyResponseCodec, + EmptyResponseData, +} from "../../../src/utils/codecs.js"; +import {compileRouteUrlFormatter} from "../../../src/utils/urlFormat.js"; +import {Endpoint, Schema} from "../../../src/utils/index.js"; +import {WireFormat} from "../../../src/index.js"; +import {HttpHeader, MediaType} from "../../../src/utils/headers.js"; +import {addSszContentTypeParser} from "../../../src/server/index.js"; /* eslint-disable @typescript-eslint/return-await */ -type User = { - id?: number; - name: string; -}; - describe("httpClient json client", () => { const afterEachCallbacks: (() => Promise | any)[] = []; afterEach(async () => { @@ -22,11 +34,21 @@ describe("httpClient json client", () => { }); const testRoute = {url: "/test-route", method: "GET" as const}; + const testDefinition: RouteDefinitionExtra = { + url: testRoute.url, + method: testRoute.method, + req: EmptyRequestCodec, + resp: EmptyResponseCodec, + operationId: "testRoute", + urlFormatter: compileRouteUrlFormatter(testRoute.url), + }; async function getServer(opts: RouteOptions): Promise<{baseUrl: string}> { const server = fastify({logger: false}); server.route(opts); + addSszContentTypeParser(server); + const reqs = new Set(); server.addHook("onRequest", async (req) => reqs.add(req.raw)); afterEachCallbacks.push(async () => { @@ -43,20 +65,46 @@ describe("httpClient json client", () => { } it("should handle successful GET request correctly", async () => { + type TestGetEndpoint = Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + {test: number}, + EmptyMeta + >; + const url = "/test-get"; + const testGetDefinition: RouteDefinitionExtra = { + url, + method: "GET", + req: EmptyRequestCodec, + resp: JsonOnlyResponseCodec, + operationId: "testGet", + urlFormatter: compileRouteUrlFormatter(url), + }; + const httpClient = await getServerWithClient({ url, method: "GET", - handler: async () => ({test: 1}), + handler: async () => ({data: {test: 1}}), }); - const {body: resBody, status} = await httpClient.json({url, method: "GET"}); + const res = await httpClient.request(testGetDefinition, undefined); - expect(status).toBe(HttpStatusCode.OK); - expect(resBody).toEqual({test: 1}); + expect(res.status).toBe(HttpStatusCode.OK); + expect(res.value()).toEqual({test: 1}); }); it("should handle successful POST request correctly", async () => { + type TestPostEndpoint = Endpoint< + "POST", + {a: string; b: string[]; c: number}, + {query: {a: string; b: string[]}; body: {c: number}}, + {test: number}, + EmptyMeta + >; + const query = {a: "a", b: ["b1", "b2"]}; const body = {c: 4}; const resBody = {test: 1}; @@ -64,20 +112,36 @@ describe("httpClient json client", () => { let bodyReceived: any; const url = "/test-post"; + const testPostDefinition: RouteDefinitionExtra = { + url, + method: "POST", + req: JsonOnlyReq({ + writeReqJson: ({a, b, c}) => ({query: {a, b}, body: {c}}), + parseReqJson: ({query, body}) => ({a: query.a, b: query.b, c: body.c}), + schema: { + query: {a: Schema.String, b: Schema.StringArray}, + body: Schema.Object, + }, + }), + resp: JsonOnlyResponseCodec, + operationId: "testPost", + urlFormatter: compileRouteUrlFormatter(url), + }; + const httpClient = await getServerWithClient({ url, method: "POST", handler: async (req) => { queryReceived = req.query; bodyReceived = req.body; - return resBody; + return {data: resBody}; }, }); - const {body: resBodyReceived, status} = await httpClient.json({url, method: "POST", query, body}); + const res = await httpClient.request(testPostDefinition, {...query, ...body}); - expect(status).toBe(HttpStatusCode.OK); - expect(resBodyReceived).toEqual(resBody); + expect(res.status).toBe(HttpStatusCode.OK); + expect(res.value()).toEqual(resBody); expect(queryReceived).toEqual(query); expect(bodyReceived).toEqual(body); }); @@ -89,14 +153,79 @@ describe("httpClient json client", () => { handler: async () => ({}), }); - try { - await httpClient.json(testRoute); - return Promise.reject(Error("did not throw")); // So it doesn't gets catch {} - } catch (e) { - if (!(e instanceof HttpError)) throw Error(`Not an HttpError: ${(e as Error).message}`); - expect(e.message).toBe("Not Found: Route GET:/test-route not found"); - expect(e.status).toBe(404); - } + const res = await httpClient.request(testDefinition, {}); + + expect(res.ok).toBe(false); + expect(res.status).toBe(404); + + expect(res.error()?.message).toBe("testRoute failed with status 404: Route GET:/test-route not found"); + }); + + it("should handle http status code 415 correctly", async () => { + const container = new ContainerType({ + a: new BooleanType(), + b: new UintNumberType(1), + }); + + type TestEndpoint = Endpoint< + "POST", + {payload: ValueOf}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; + + const url = "/test-unsupported-media-type"; + const routeId = "testUnsupportedMediaType"; + const testPostDefinition: RouteDefinitionExtra = { + url, + method: "POST", + req: { + writeReqJson: ({payload}) => ({body: container.toJson(payload)}), + parseReqJson: ({body}) => ({payload: container.fromJson(body)}), + writeReqSsz: ({payload}) => ({body: container.serialize(payload)}), + parseReqSsz: ({body}) => ({payload: container.deserialize(body)}), + schema: { + body: Schema.Object, + }, + }, + resp: EmptyResponseCodec, + operationId: routeId, + urlFormatter: compileRouteUrlFormatter(url), + }; + + const httpClient = await getServerWithClient({ + url, + method: "POST", + handler: async (req, res) => { + if (req.headers[HttpHeader.ContentType] !== MediaType.json) { + void res.status(415); + } + }, + }); + const fetchSpy = vi.spyOn(httpClient, "fetch" as any); + const sszNotSupportedCache = httpClient["sszNotSupportedByRouteIdByUrlIndex"].getOrDefault(0); + + const res1 = await httpClient.request( + testPostDefinition, + {payload: {a: true, b: 1}}, + {requestWireFormat: WireFormat.ssz} + ); + + expect(res1.ok).toBe(true); + expect(fetchSpy).toHaveBeenCalledTimes(2); + expect(sszNotSupportedCache?.has(routeId)).toBe(true); + + // Subsequent requests should always use JSON + const res2 = await httpClient.request( + testPostDefinition, + {payload: {a: true, b: 1}}, + {requestWireFormat: WireFormat.ssz} + ); + + expect(res2.ok).toBe(true); + // Call count should only be incremented by 1, no retry + expect(fetchSpy).toHaveBeenCalledTimes(3); }); it("should handle http status code 500 correctly", async () => { @@ -107,32 +236,122 @@ describe("httpClient json client", () => { }, }); - try { - await httpClient.json(testRoute); - return Promise.reject(Error("did not throw")); - } catch (e) { - if (!(e instanceof HttpError)) throw Error(`Not an HttpError: ${(e as Error).message}`); - expect(e.message).toBe("Internal Server Error: Test error"); - expect(e.status).toBe(500); - } + const res = await httpClient.request(testDefinition, {}); + + expect(res.ok).toBe(false); + expect(res.status).toBe(500); + + expect(res.error()?.message).toBe("testRoute failed with status 500: Test error"); }); it("should handle http status with custom code 503", async () => { const httpClient = await getServerWithClient({ ...testRoute, - handler: async (req, res) => { + handler: async (_req, res) => { return res.code(503).send("Node is syncing"); }, }); - try { - await httpClient.json(testRoute); - return Promise.reject(Error("did not throw")); - } catch (e) { - if (!(e instanceof HttpError)) throw Error(`Not an HttpError: ${(e as Error).message}`); - expect(e.message).toBe("Service Unavailable: Node is syncing"); - expect(e.status).toBe(503); - } + const res = await httpClient.request(testDefinition, {}); + + expect(res.ok).toBe(false); + expect(res.status).toBe(503); + + expect(res.error()?.message).toBe("testRoute failed with status 503: Node is syncing"); + }); + + it("should send a SSZ-serialized request if configured as wire format", async () => { + const container = new ContainerType({ + a: new BooleanType(), + b: new UintNumberType(1), + }); + + type TestEndpoint = Endpoint< + "POST", + {payload: ValueOf}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; + + const url = "/test-ssz-wire-format"; + const routeId = "testSszWireFormat"; + const testPostDefinition: RouteDefinitionExtra = { + url, + method: "POST", + req: { + writeReqJson: ({payload}) => ({body: container.toJson(payload)}), + parseReqJson: ({body}) => ({payload: container.fromJson(body)}), + writeReqSsz: ({payload}) => ({body: container.serialize(payload)}), + parseReqSsz: ({body}) => ({payload: container.deserialize(body)}), + schema: { + body: Schema.Object, + }, + }, + resp: EmptyResponseCodec, + operationId: routeId, + urlFormatter: compileRouteUrlFormatter(url), + }; + const payload = {a: true, b: 1}; + + const httpClient = await getServerWithClient({ + url, + method: "POST", + handler: async (req) => { + expect(req.headers[HttpHeader.ContentType]).toBe(MediaType.ssz); + expect(req.body).toBeInstanceOf(Uint8Array); + expect(container.deserialize(req.body as Uint8Array)).toEqual(payload); + }, + }); + + (await httpClient.request(testPostDefinition, {payload}, {requestWireFormat: WireFormat.ssz})).assertOk(); + }); + + it("should send a JSON-serialized request if configured as wire format", async () => { + const container = new ContainerType({ + a: new BooleanType(), + b: new UintNumberType(1), + }); + + type TestEndpoint = Endpoint< + "POST", + {payload: ValueOf}, + {body: unknown}, + EmptyResponseData, + EmptyMeta + >; + + const url = "/test-json-wire-format"; + const routeId = "testJsonWireFormat"; + const testPostDefinition: RouteDefinitionExtra = { + url, + method: "POST", + req: { + writeReqJson: ({payload}) => ({body: container.toJson(payload)}), + parseReqJson: ({body}) => ({payload: container.fromJson(body)}), + writeReqSsz: ({payload}) => ({body: container.serialize(payload)}), + parseReqSsz: ({body}) => ({payload: container.deserialize(body)}), + schema: { + body: Schema.Object, + }, + }, + resp: EmptyResponseCodec, + operationId: routeId, + urlFormatter: compileRouteUrlFormatter(url), + }; + const payload = {a: true, b: 1}; + + const httpClient = await getServerWithClient({ + url, + method: "POST", + handler: async (req) => { + expect(req.headers[HttpHeader.ContentType]).toBe(MediaType.json); + expect(req.body).toBeInstanceOf(Object); + expect(container.fromJson(req.body)).toEqual(payload); + }, + }); + + (await httpClient.request(testPostDefinition, {payload}, {requestWireFormat: WireFormat.json})).assertOk(); }); it("should set user credentials in URL as Authorization header", async () => { @@ -148,7 +367,7 @@ describe("httpClient json client", () => { url.password = "password"; const httpClient = new HttpClient({baseUrl: url.toString()}); - await httpClient.json(testRoute); + (await httpClient.request(testDefinition, {})).assertOk(); }); it("should not URI-encode user credentials in Authorization header", async () => { @@ -171,7 +390,7 @@ describe("httpClient json client", () => { const httpClient = new HttpClient({baseUrl: baseUrl}); - await httpClient.json(testRoute); + (await httpClient.request(testDefinition, {})).assertOk(); }); it("should handle aborting request with timeout", async () => { @@ -180,10 +399,10 @@ describe("httpClient json client", () => { handler: async () => new Promise((r) => setTimeout(r, 1000)), }); - const httpClient = new HttpClient({baseUrl, timeoutMs: 10}); + const httpClient = new HttpClient({baseUrl, globalInit: {timeoutMs: 10}}); try { - await httpClient.json(testRoute); + await httpClient.request(testDefinition, {}); return Promise.reject(Error("did not throw")); } catch (e) { if (!(e instanceof TimeoutError)) throw Error(`Not an TimeoutError: ${(e as Error).message}`); @@ -197,13 +416,12 @@ describe("httpClient json client", () => { }); const controller = new AbortController(); - const signal = controller.signal; - const httpClient = new HttpClient({baseUrl, getAbortSignal: () => signal}); + const httpClient = new HttpClient({baseUrl, globalInit: {signal: controller.signal}}); setTimeout(() => controller.abort(), 10); try { - await httpClient.json(testRoute); + await httpClient.request(testDefinition, {}); return Promise.reject(Error("did not throw")); } catch (e) { if (!(e instanceof ErrorAborted)) throw Error(`Not an ErrorAborted: ${(e as Error).message}`); diff --git a/packages/api/test/unit/client/httpClientFallback.test.ts b/packages/api/test/unit/client/httpClientFallback.test.ts index e51119741d3c..10f4a60c678a 100644 --- a/packages/api/test/unit/client/httpClientFallback.test.ts +++ b/packages/api/test/unit/client/httpClientFallback.test.ts @@ -1,8 +1,18 @@ -import {describe, it, beforeEach, afterEach, expect, vi} from "vitest"; -import {HttpClient, fetch} from "../../../src/utils/client/index.js"; +import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"; +import {HttpClient, RouteDefinitionExtra, fetch} from "../../../src/utils/client/index.js"; +import {AnyEndpoint, EmptyRequestCodec, EmptyResponseCodec} from "../../../src/utils/codecs.js"; +import {compileRouteUrlFormatter} from "../../../src/utils/urlFormat.js"; describe("httpClient fallback", () => { - const testRoute = {url: "/test-route", method: "GET" as const}; + const url = "/test-route"; + const testDefinition: RouteDefinitionExtra = { + url, + method: "GET", + req: EmptyRequestCodec, + resp: EmptyResponseCodec, + operationId: "testRoute", + urlFormatter: compileRouteUrlFormatter(url), + }; const DEBUG_LOGS = Boolean(process.env.DEBUG); // Using fetchSub instead of actually setting up servers because there are some strange @@ -41,9 +51,16 @@ describe("httpClient fallback", () => { await new Promise((r) => setTimeout(r, 10)); const i = getServerIndex(url); if (serverErrors.get(i)) { - throw Error(`test_error_server_${i}`); + if (i === 1) { + // Simulate one of the servers returning a HTTP error + // which is handled separately from network errors + // but the fallback logic should be the same + return new Response(null, {status: 500}); + } else { + throw Error(`test_error_server_${i}`); + } } else { - return {ok: true} as Response; + return new Response(null, {status: 200}); } }); }); @@ -69,7 +86,7 @@ describe("httpClient fallback", () => { } async function requestTestRoute(): Promise { - await httpClient.request(testRoute); + await httpClient.request(testDefinition, {}, {}); } it("Should only call server 0", async () => { diff --git a/packages/api/test/unit/client/httpClientOptions.test.ts b/packages/api/test/unit/client/httpClientOptions.test.ts index af0968777219..9bc7ab2bfaf8 100644 --- a/packages/api/test/unit/client/httpClientOptions.test.ts +++ b/packages/api/test/unit/client/httpClientOptions.test.ts @@ -8,21 +8,28 @@ describe("HTTPClient options", () => { const bearerToken2 = "token-2"; it("Single root baseUrl option", () => { - const httpClient = new HttpClient({baseUrl: baseUrl1, bearerToken: bearerToken1}); + const httpClient = new HttpClient({baseUrl: baseUrl1, globalInit: {bearerToken: bearerToken1}}); - expect(httpClient["urlsOpts"]).toEqual([{baseUrl: baseUrl1, bearerToken: bearerToken1}]); + const [urlInit] = httpClient["urlsInits"]; + + expect(urlInit.baseUrl).toBe(baseUrl1); + expect(urlInit.bearerToken).toBe(bearerToken1); }); it("Multiple urls option with common bearerToken", () => { const httpClient = new HttpClient({ urls: [baseUrl1, baseUrl2], - bearerToken: bearerToken1, + globalInit: { + bearerToken: bearerToken1, + }, }); - expect(httpClient["urlsOpts"]).toEqual([ - {baseUrl: baseUrl1, bearerToken: bearerToken1}, - {baseUrl: baseUrl2, bearerToken: bearerToken1}, - ]); + const [urlInit1, urlInit2] = httpClient["urlsInits"]; + + expect(urlInit1.baseUrl).toBe(baseUrl1); + expect(urlInit1.bearerToken).toBe(bearerToken1); + expect(urlInit2.baseUrl).toBe(baseUrl2); + expect(urlInit2.bearerToken).toBe(bearerToken1); }); it("Multiple urls as object option", () => { @@ -33,39 +40,47 @@ describe("HTTPClient options", () => { ], }); - expect(httpClient["urlsOpts"]).toEqual([ - {baseUrl: baseUrl1, bearerToken: bearerToken1}, - {baseUrl: baseUrl2, bearerToken: bearerToken2}, - ]); + const [urlInit1, urlInit2] = httpClient["urlsInits"]; + + expect(urlInit1.baseUrl).toBe(baseUrl1); + expect(urlInit1.bearerToken).toBe(bearerToken1); + expect(urlInit2.baseUrl).toBe(baseUrl2); + expect(urlInit2.bearerToken).toBe(bearerToken2); }); it("baseUrl and urls option", () => { const httpClient = new HttpClient({ baseUrl: baseUrl1, - bearerToken: bearerToken1, + globalInit: {bearerToken: bearerToken1}, urls: [{baseUrl: baseUrl2, bearerToken: bearerToken2}], }); - expect(httpClient["urlsOpts"]).toEqual([ - {baseUrl: baseUrl1, bearerToken: bearerToken1}, - {baseUrl: baseUrl2, bearerToken: bearerToken2}, - ]); + const [urlInit1, urlInit2] = httpClient["urlsInits"]; + + expect(urlInit1.baseUrl).toBe(baseUrl1); + expect(urlInit1.bearerToken).toBe(bearerToken1); + expect(urlInit2.baseUrl).toBe(baseUrl2); + expect(urlInit2.bearerToken).toBe(bearerToken2); }); it("de-duplicate urls", () => { const httpClient = new HttpClient({ baseUrl: baseUrl1, - bearerToken: bearerToken1, + globalInit: {bearerToken: bearerToken1}, urls: [ {baseUrl: baseUrl2, bearerToken: bearerToken2}, {baseUrl: baseUrl1, bearerToken: bearerToken1}, {baseUrl: baseUrl2, bearerToken: bearerToken2}, ], }); - expect(httpClient["urlsOpts"]).toEqual([ - {baseUrl: baseUrl1, bearerToken: bearerToken1}, - {baseUrl: baseUrl2, bearerToken: bearerToken2}, - ]); + + const [urlInit1, urlInit2, urlInit3] = httpClient["urlsInits"]; + + expect(urlInit1.baseUrl).toBe(baseUrl1); + expect(urlInit1.bearerToken).toBe(bearerToken1); + expect(urlInit2.baseUrl).toBe(baseUrl2); + expect(urlInit2.bearerToken).toBe(bearerToken2); + expect(urlInit3).toBeUndefined(); }); it("Throw if empty baseUrl", () => { diff --git a/packages/api/test/unit/client/urlFormat.test.ts b/packages/api/test/unit/client/urlFormat.test.ts index 5b8e1f294976..dc86e2674e13 100644 --- a/packages/api/test/unit/client/urlFormat.test.ts +++ b/packages/api/test/unit/client/urlFormat.test.ts @@ -1,6 +1,6 @@ import {describe, it, expect} from "vitest"; import { - compileRouteUrlFormater, + compileRouteUrlFormatter, toColonNotationPath, Token, TokenType, @@ -59,10 +59,10 @@ describe("utils / urlFormat", () => { expect(toColonNotationPath(urlTemplate)).toBe(colonNotation); - const utlFormater = compileRouteUrlFormater(urlTemplate); + const urlFormatter = compileRouteUrlFormatter(urlTemplate); for (const [_, {args, url}] of cases.entries()) { - expect(utlFormater(args)).toBe(url); + expect(urlFormatter(args)).toBe(url); } }); } diff --git a/packages/api/test/unit/keymanager/keymanager.test.ts b/packages/api/test/unit/keymanager/keymanager.test.ts index 1adf5b1e44da..42e6a3dbf511 100644 --- a/packages/api/test/unit/keymanager/keymanager.test.ts +++ b/packages/api/test/unit/keymanager/keymanager.test.ts @@ -1,11 +1,11 @@ import {describe} from "vitest"; import {config} from "@lodestar/config/default"; -import {Api, ReqTypes} from "../../../src/keymanager/routes.js"; +import {Endpoints} from "../../../src/keymanager/routes.js"; import {getClient} from "../../../src/keymanager/client.js"; import {getRoutes} from "../../../src/keymanager/server/index.js"; import {runGenericServerTest} from "../../utils/genericServerTest.js"; import {testData} from "./testData.js"; describe("keymanager", () => { - runGenericServerTest(config, getClient, getRoutes, testData); + runGenericServerTest(config, getClient, getRoutes, testData); }); diff --git a/packages/api/test/unit/keymanager/oapiSpec.test.ts b/packages/api/test/unit/keymanager/oapiSpec.test.ts index aec70bc64858..10c29f0cea55 100644 --- a/packages/api/test/unit/keymanager/oapiSpec.test.ts +++ b/packages/api/test/unit/keymanager/oapiSpec.test.ts @@ -1,7 +1,8 @@ import path from "node:path"; import {fileURLToPath} from "node:url"; +import {config} from "@lodestar/config/default"; import {OpenApiFile} from "../../utils/parseOpenApiSpec.js"; -import {routesData, getReqSerializers, getReturnTypes} from "../../../src/keymanager/routes.js"; +import {getDefinitions} from "../../../src/keymanager/routes.js"; import {runTestCheckAgainstSpec} from "../../utils/checkAgainstSpec.js"; import {fetchOpenApiSpec} from "../../utils/fetchOpenApiSpec.js"; import {testData} from "./testData.js"; @@ -18,9 +19,5 @@ const openApiFile: OpenApiFile = { version: RegExp(version), }; -// TODO: un-skip in follow-up PR, this PR only adds basic infra for spec testing -const reqSerializers = getReqSerializers(); -const returnTypes = getReturnTypes(); - const openApiJson = await fetchOpenApiSpec(openApiFile); -runTestCheckAgainstSpec(openApiJson, routesData, reqSerializers, returnTypes, testData); +runTestCheckAgainstSpec(openApiJson, getDefinitions(config), testData); diff --git a/packages/api/test/unit/keymanager/testData.ts b/packages/api/test/unit/keymanager/testData.ts index 2c66610c8733..bd37798c4685 100644 --- a/packages/api/test/unit/keymanager/testData.ts +++ b/packages/api/test/unit/keymanager/testData.ts @@ -1,8 +1,8 @@ import {ssz} from "@lodestar/types"; import { - Api, DeleteRemoteKeyStatus, DeletionStatus, + Endpoints, ImportRemoteKeyStatus, ImportStatus, } from "../../../src/keymanager/routes.js"; @@ -15,9 +15,9 @@ const graffitiRandUtf8 = "636861696e736166652f6c6f64657374"; const gasLimitRand = 30_000_000; const builderBoostFactorRand = BigInt(100); -export const testData: GenericServerTestCases = { +export const testData: GenericServerTestCases = { listKeys: { - args: [], + args: undefined, res: { data: [ { @@ -29,16 +29,16 @@ export const testData: GenericServerTestCases = { }, }, importKeystores: { - args: [[pubkeyRand], ["pass1"], "slash_protection"], + args: {keystores: ["keystore"], passwords: ["pass1"], slashingProtection: "slash_protection"}, res: {data: [{status: ImportStatus.imported}]}, }, deleteKeys: { - args: [[pubkeyRand]], - res: {data: [{status: DeletionStatus.deleted}], slashingProtection: "slash_protection"}, + args: {pubkeys: [pubkeyRand]}, + res: {data: {statuses: [{status: DeletionStatus.deleted}], slashingProtection: "slash_protection"}}, }, listRemoteKeys: { - args: [], + args: undefined, res: { data: [ { @@ -50,66 +50,66 @@ export const testData: GenericServerTestCases = { }, }, importRemoteKeys: { - args: [[{pubkey: pubkeyRand, url: "https://sign.er"}]], + args: {remoteSigners: [{pubkey: pubkeyRand, url: "https://sign.er"}]}, res: {data: [{status: ImportRemoteKeyStatus.imported}]}, }, deleteRemoteKeys: { - args: [[pubkeyRand]], + args: {pubkeys: [pubkeyRand]}, res: {data: [{status: DeleteRemoteKeyStatus.deleted}]}, }, listFeeRecipient: { - args: [pubkeyRand], + args: {pubkey: pubkeyRand}, res: {data: {pubkey: pubkeyRand, ethaddress: ethaddressRand}}, }, setFeeRecipient: { - args: [pubkeyRand, ethaddressRand], + args: {pubkey: pubkeyRand, ethaddress: ethaddressRand}, res: undefined, }, deleteFeeRecipient: { - args: [pubkeyRand], + args: {pubkey: pubkeyRand}, res: undefined, }, - listGraffiti: { - args: [pubkeyRand], + getGraffiti: { + args: {pubkey: pubkeyRand}, res: {data: {pubkey: pubkeyRand, graffiti: graffitiRandUtf8}}, }, setGraffiti: { - args: [pubkeyRand, graffitiRandUtf8], + args: {pubkey: pubkeyRand, graffiti: graffitiRandUtf8}, res: undefined, }, deleteGraffiti: { - args: [pubkeyRand], + args: {pubkey: pubkeyRand}, res: undefined, }, getGasLimit: { - args: [pubkeyRand], + args: {pubkey: pubkeyRand}, res: {data: {pubkey: pubkeyRand, gasLimit: gasLimitRand}}, }, setGasLimit: { - args: [pubkeyRand, gasLimitRand], + args: {pubkey: pubkeyRand, gasLimit: gasLimitRand}, res: undefined, }, deleteGasLimit: { - args: [pubkeyRand], + args: {pubkey: pubkeyRand}, res: undefined, }, signVoluntaryExit: { - args: [pubkeyRand, 1], + args: {pubkey: pubkeyRand, epoch: 1}, res: {data: ssz.phase0.SignedVoluntaryExit.defaultValue()}, }, getBuilderBoostFactor: { - args: [pubkeyRand], + args: {pubkey: pubkeyRand}, res: {data: {pubkey: pubkeyRand, builderBoostFactor: builderBoostFactorRand}}, }, setBuilderBoostFactor: { - args: [pubkeyRand, builderBoostFactorRand], + args: {pubkey: pubkeyRand, builderBoostFactor: builderBoostFactorRand}, res: undefined, }, deleteBuilderBoostFactor: { - args: [pubkeyRand], + args: {pubkey: pubkeyRand}, res: undefined, }, }; diff --git a/packages/api/test/unit/utils/acceptHeader.test.ts b/packages/api/test/unit/utils/headers.test.ts similarity index 56% rename from packages/api/test/unit/utils/acceptHeader.test.ts rename to packages/api/test/unit/utils/headers.test.ts index b93f07ba286d..c909fe59499b 100644 --- a/packages/api/test/unit/utils/acceptHeader.test.ts +++ b/packages/api/test/unit/utils/headers.test.ts @@ -1,35 +1,35 @@ import {describe, it, expect} from "vitest"; -import {parseAcceptHeader} from "../../../src/utils/acceptHeader.js"; -import {ResponseFormat} from "../../../src/interfaces.js"; +import {MediaType, SUPPORTED_MEDIA_TYPES, parseAcceptHeader} from "../../../src/utils/headers.js"; -describe("utils / acceptHeader", () => { +describe("utils / headers", () => { describe("parseAcceptHeader", () => { - const testCases: {header: string | undefined; expected: ResponseFormat}[] = [ - {header: undefined, expected: "json"}, - {header: "application/json", expected: "json"}, - {header: "application/octet-stream", expected: "ssz"}, - {header: "application/invalid", expected: "json"}, - {header: "application/invalid;q=1,application/octet-stream;q=0.1", expected: "ssz"}, - {header: "application/octet-stream;q=0.5,application/json;q=1", expected: "json"}, - {header: "application/octet-stream;q=1,application/json;q=0.1", expected: "ssz"}, - {header: "application/octet-stream,application/json;q=0.1", expected: "ssz"}, - {header: "application/octet-stream;,application/json;q=0.1", expected: "json"}, - {header: "application/octet-stream;q=2,application/json;q=0.1", expected: "json"}, - {header: "application/octet-stream;q=invalid,application/json;q=0.1", expected: "json"}, - {header: "application/octet-stream;q=invalid,application/json;q=0.1", expected: "json"}, - {header: "application/octet-stream ; q=0.5 , application/json ; q=1", expected: "json"}, - {header: "application/octet-stream ; q=1 , application/json ; q=0.1", expected: "ssz"}, - {header: "application/octet-stream;q=1,application/json;q=0.1", expected: "ssz"}, + const testCases: {header: string | undefined; expected: MediaType | null}[] = [ + {header: undefined, expected: null}, + {header: "*/*", expected: null}, + {header: "application/json", expected: MediaType.json}, + {header: "application/octet-stream", expected: MediaType.ssz}, + {header: "application/invalid", expected: null}, + {header: "application/invalid;q=1,application/octet-stream;q=0.1", expected: MediaType.ssz}, + {header: "application/octet-stream;q=0.5,application/json;q=1", expected: MediaType.json}, + {header: "application/octet-stream;q=1,application/json;q=0.1", expected: MediaType.ssz}, + {header: "application/octet-stream,application/json;q=0.1", expected: MediaType.ssz}, + {header: "application/octet-stream;,application/json;q=0.1", expected: MediaType.json}, + {header: "application/octet-stream;q=2,application/json;q=0.1", expected: MediaType.json}, + {header: "application/octet-stream;q=invalid,application/json;q=0.1", expected: MediaType.json}, + {header: "application/octet-stream;q=invalid,application/json;q=0.1", expected: MediaType.json}, + {header: "application/octet-stream ; q=0.5 , application/json ; q=1", expected: MediaType.json}, + {header: "application/octet-stream ; q=1 , application/json ; q=0.1", expected: MediaType.ssz}, + {header: "application/octet-stream;q=1,application/json;q=0.1", expected: MediaType.ssz}, // The implementation is order dependent, however, RFC-9110 doesn't specify a preference. // The following tests serve to document the behavior at the time of implementation- not a // specific requirement from the spec. In this case, last wins. - {header: "application/octet-stream;q=1,application/json;q=1", expected: "json"}, - {header: "application/json;q=1,application/octet-stream;q=1", expected: "ssz"}, + {header: "application/octet-stream;q=1,application/json;q=1", expected: MediaType.json}, + {header: "application/json;q=1,application/octet-stream;q=1", expected: MediaType.ssz}, ]; it.each(testCases)("should correctly parse the header $header", ({header, expected}) => { - expect(parseAcceptHeader(header)).toBe(expected); + expect(parseAcceptHeader(header, SUPPORTED_MEDIA_TYPES)).toBe(expected); }); }); }); diff --git a/packages/api/test/utils/checkAgainstSpec.ts b/packages/api/test/utils/checkAgainstSpec.ts index 354ae53b2358..5e339efcb822 100644 --- a/packages/api/test/utils/checkAgainstSpec.ts +++ b/packages/api/test/utils/checkAgainstSpec.ts @@ -1,6 +1,7 @@ import Ajv, {ErrorObject} from "ajv"; import {expect, describe, beforeAll, it} from "vitest"; -import {ReqGeneric, ReqSerializer, ReturnTypes, RouteDef} from "../../src/utils/types.js"; +import {WireFormat} from "../../src/utils/wireFormat.js"; +import {Endpoint, RequestWithBodyCodec, RouteDefinitions, isRequestWithoutBody} from "../../src/utils/types.js"; import {applyRecursively, JsonSchema, OpenApiJson, parseOpenApiSpec} from "./parseOpenApiSpec.js"; import {GenericServerTestCases} from "./genericServerTest.js"; @@ -60,12 +61,10 @@ function deleteNested(schema: JsonSchema | undefined, property: string): void { } } -export function runTestCheckAgainstSpec( +export function runTestCheckAgainstSpec>( openApiJson: OpenApiJson, - routesData: Record, - reqSerializers: Record>, - returnTypes: Record[string]>, - testDatas: Record[string]>, + definitions: RouteDefinitions, + testCases: GenericServerTestCases, ignoredOperations: string[] = [], ignoredProperties: Record = {} ): void { @@ -82,12 +81,12 @@ export function runTestCheckAgainstSpec( describe(operationId, () => { const {requestSchema, responseOkSchema} = routeSpec; const routeId = operationId; - const testData = testDatas[routeId]; - const routeData = routesData[routeId]; + const testData = testCases[routeId]; + const routeDef = definitions[routeId]; beforeAll(() => { - if (routeData == null) { - throw Error(`No routeData for ${routeId}`); + if (routeDef == null) { + throw Error(`No routeDef for ${routeId}`); } if (testData == null) { throw Error(`No testData for ${routeId}`); @@ -95,18 +94,19 @@ export function runTestCheckAgainstSpec( }); it(`${operationId}_route`, function () { - expect(routeData.method.toLowerCase()).to.equal(routeSpec.method.toLowerCase(), "Wrong method"); - expect(routeData.url).to.equal(routeSpec.url, "Wrong url"); + expect(routeDef.method.toLowerCase()).toBe(routeSpec.method.toLowerCase()); + expect(routeDef.url).toBe(routeSpec.url); }); if (requestSchema != null) { it(`${operationId}_request`, function () { - const reqJson = reqSerializers[routeId].writeReq(...(testData.args as [never])) as unknown; + const reqJson = isRequestWithoutBody(routeDef) + ? routeDef.req.writeReq(testData.args) + : (routeDef.req as RequestWithBodyCodec).writeReqJson(testData.args); // Stringify param and query to simulate rendering in HTTP query - // TODO: Review conversions in fastify and other servers - stringifyProperties((reqJson as ReqGeneric).params ?? {}); - stringifyProperties((reqJson as ReqGeneric).query ?? {}); + stringifyProperties(reqJson.params ?? {}); + stringifyProperties(reqJson.query ?? {}); const ignoredProperties = ignoredProperty?.request; if (ignoredProperties) { @@ -118,12 +118,37 @@ export function runTestCheckAgainstSpec( // Validate request validateSchema(routeSpec.requestSchema, reqJson, "request"); + + // Verify that request supports ssz if required by spec + if (routeSpec.requestSszRequired) { + try { + const reqCodec = routeDef.req as RequestWithBodyCodec; + const reqSsz = reqCodec.writeReqSsz(testData.args); + + expect(reqSsz.body).toBeInstanceOf(Uint8Array); + expect(reqCodec.onlySupport).not.toBe(WireFormat.json); + } catch { + throw Error("Must support ssz request body"); + } + } }); } if (responseOkSchema) { it(`${operationId}_response`, function () { - const resJson = returnTypes[operationId].toJson(testData.res as any); + const data = routeDef.resp.data.toJson(testData.res?.data, testData.res?.meta); + const metaJson = routeDef.resp.meta.toJson(testData.res?.meta); + const headers = parseHeaders(routeDef.resp.meta.toHeadersObject(testData.res?.meta)); + + let resJson: unknown; + if (routeDef.resp.transform) { + resJson = routeDef.resp.transform.toResponse(data, metaJson); + } else { + resJson = { + data, + ...(metaJson as object), + }; + } const ignoredProperties = ignoredProperty?.response; if (ignoredProperties) { @@ -133,7 +158,19 @@ export function runTestCheckAgainstSpec( } } // Validate response - validateSchema(responseOkSchema, resJson, "response"); + validateSchema(responseOkSchema, {headers, body: resJson}, "response"); + + // Verify that response supports ssz if required by spec + if (routeSpec.responseSszRequired) { + try { + const sszBytes = routeDef.resp.data.serialize(testData.res?.data, testData.res?.meta); + + expect(sszBytes).toBeInstanceOf(Uint8Array); + expect(routeDef.resp.onlySupport).not.toBe(WireFormat.json); + } catch { + throw Error("Must support ssz response body"); + } + } }); } }); @@ -196,3 +233,16 @@ function stringifyProperties(obj: Record): Record): Record { + const parsed: Record = {}; + for (const key of Object.keys(headers)) { + const value = headers[key]; + parsed[key] = /true|false/.test(value) ? value === "true" : value; + } + return parsed; +} diff --git a/packages/api/test/utils/genericServerTest.ts b/packages/api/test/utils/genericServerTest.ts index 4cd0263aaea8..1c01bb449306 100644 --- a/packages/api/test/utils/genericServerTest.ts +++ b/packages/api/test/utils/genericServerTest.ts @@ -1,49 +1,41 @@ -import {it, expect, MockInstance, describe, beforeAll, afterAll} from "vitest"; +import {it, expect, describe, beforeAll, afterAll, MockInstance} from "vitest"; import {FastifyInstance} from "fastify"; import {ChainForkConfig} from "@lodestar/config"; -import {ReqGeneric, Resolves} from "../../src/utils/index.js"; -import {FetchOpts, HttpClient, IHttpClient} from "../../src/utils/client/index.js"; -import {ServerRoutes} from "../../src/utils/server/genericJsonServer.js"; -import {registerRoute} from "../../src/utils/server/registerRoute.js"; -import {HttpStatusCode} from "../../src/utils/client/httpStatusCode.js"; -import {APIClientHandler, ApiClientResponseData, ServerApi} from "../../src/interfaces.js"; +import {Endpoint} from "../../src/utils/index.js"; +import {WireFormat} from "../../src/utils/wireFormat.js"; +import {ApplicationMethods, ApplicationResponse, FastifyRoutes} from "../../src/utils/server/index.js"; +import {ApiClientMethods, ApiRequestInit, HttpClient, IHttpClient} from "../../src/utils/client/index.js"; import {getMockApi, getTestServer} from "./utils.js"; -type IgnoreVoid = T extends void ? undefined : T; - -export type GenericServerTestCases> = { - [K in keyof Api]: { - args: Parameters; - res: IgnoreVoid>>; - query?: FetchOpts["query"]; +export type GenericServerTestCases> = { + [K in keyof Es]: { + args: Es[K]["args"]; + res: ApplicationResponse; }; }; -export function runGenericServerTest< - Api extends Record, - ReqTypes extends {[K in keyof Api]: ReqGeneric}, ->( +export function runGenericServerTest>( config: ChainForkConfig, - getClient: (config: ChainForkConfig, https: IHttpClient) => Api, - getRoutes: (config: ChainForkConfig, api: ServerApi) => ServerRoutes, ReqTypes>, - testCases: GenericServerTestCases + getClient: (config: ChainForkConfig, http: IHttpClient) => ApiClientMethods, + getRoutes: (config: ChainForkConfig, methods: ApplicationMethods) => FastifyRoutes, + testCases: GenericServerTestCases ): void { - const mockApi = getMockApi(testCases); + const mockApi = getMockApi(testCases); let server: FastifyInstance; - let client: Api; - let httpClient: HttpClientSpy; + let client: ApiClientMethods; + let httpClient: HttpClient; beforeAll(async () => { const res = getTestServer(); server = res.server; for (const route of Object.values(getRoutes(config, mockApi))) { - registerRoute(server, route); + server.route(route); } const baseUrl = await res.start(); - httpClient = new HttpClientSpy({baseUrl}); + httpClient = new HttpClient({baseUrl}); client = getClient(config, httpClient); }); @@ -52,49 +44,30 @@ export function runGenericServerTest< }); describe("run generic server tests", () => { - it.each(Object.keys(testCases))("%s", async (key) => { - const routeId = key as keyof Api; - const testCase = testCases[routeId]; - - // Register mock data for this route - // TODO: Look for the type error - (mockApi[routeId] as MockInstance).mockResolvedValue(testCases[routeId].res); - - // Do the call - const res = await client[routeId](...(testCase.args as any[])); - - // Use spy to assert argument serialization - if (testCase.query) { - expect(httpClient.opts?.query).toEqual(testCase.query); - } - - // Assert server handler called with correct args - expect(mockApi[routeId] as MockInstance).toHaveBeenCalledTimes(1); - - // if mock api args are > testcase args, there may be some undefined extra args parsed towards the end - // to obtain a match, ignore the extra args - expect(mockApi[routeId] as MockInstance).toHaveBeenNthCalledWith(1, ...(testCase.args as any[])); - - // Assert returned value is correct - expect(res.response).toEqual(testCase.res); + describe.each(Object.keys(testCases))("%s", (key) => { + it.each(Object.values(WireFormat))("%s", async (format) => { + const wireFormat = format as WireFormat; + const localInit: ApiRequestInit = { + requestWireFormat: wireFormat, + responseWireFormat: wireFormat, + }; + const routeId = key as keyof Es; + const testCase = testCases[routeId]; + + // Register mock data for this route + (mockApi[routeId] as MockInstance).mockResolvedValue(testCases[routeId].res); + + // Do the call + const res = await client[routeId](testCase.args ?? localInit, localInit); + + // Assert server handler called with correct args + expect(mockApi[routeId]).toHaveBeenCalledTimes(1); + expect(mockApi[routeId]).toHaveBeenCalledWith(testCase.args, expect.any(Object)); + + // Assert returned value and metadata is correct + expect(res.value()).toEqual(testCase.res?.data); + expect(res.meta()).toEqual(testCase.res?.meta); + }); }); }); } - -class HttpClientSpy extends HttpClient { - opts: FetchOpts | null = null; - - async json(opts: FetchOpts): Promise<{status: HttpStatusCode; body: T}> { - this.opts = opts; - return super.json(opts); - } - async arrayBuffer(opts: FetchOpts): Promise<{status: HttpStatusCode; body: ArrayBuffer}> { - this.opts = opts; - return super.arrayBuffer(opts); - } - - async request(opts: FetchOpts): Promise<{status: HttpStatusCode; body: void}> { - this.opts = opts; - return super.request(opts); - } -} diff --git a/packages/api/test/utils/parseOpenApiSpec.ts b/packages/api/test/utils/parseOpenApiSpec.ts index 2672b381eea6..7527fb61abaa 100644 --- a/packages/api/test/utils/parseOpenApiSpec.ts +++ b/packages/api/test/utils/parseOpenApiSpec.ts @@ -47,14 +47,17 @@ type RouteDefinition = { operationId: string; parameters: { name: string; - in: "path" | "query"; + in: "path" | "query" | "header"; schema: JsonSchema; }[]; responses: { /** `"200"` | `"500"` */ - [statusCode: string]: { - content?: Content; - }; + [statusCode: string]: + | { + headers?: Record; + content?: Content; + } + | undefined; }; requestBody?: { content?: Content; @@ -65,12 +68,20 @@ export type RouteSpec = { url: RouteUrl; method: HttpMethod; responseOkSchema: JsonSchema | undefined; + responseSszRequired: boolean; requestSchema: JsonSchema; + requestSszRequired: boolean; }; export type ReqSchema = { params?: JsonSchema; query?: JsonSchema; + headers?: JsonSchema; + body?: JsonSchema; +}; + +export type RespSchema = { + headers?: JsonSchema; body?: JsonSchema; }; @@ -80,6 +91,7 @@ enum StatusCode { enum ContentType { json = "application/json", + ssz = "application/octet-stream", } export function parseOpenApiSpec(openApiJson: OpenApiJson): Map { @@ -87,27 +99,30 @@ export function parseOpenApiSpec(openApiJson: OpenApiJson): Map void) } function buildReqSchema(routeDefinition: RouteDefinition): JsonSchema { - const reqSchemas: ReqSchema = {}; + const reqSchema: ReqSchema = {}; // "parameters": [{ // "name": "block_id", @@ -182,30 +197,69 @@ function buildReqSchema(routeDefinition: RouteDefinition): JsonSchema { for (const parameter of routeDefinition.parameters ?? []) { switch (parameter.in) { case "path": - if (!reqSchemas.params) reqSchemas.params = {type: "object", properties: {}}; - if (!reqSchemas.params.properties) reqSchemas.params.properties = {}; - reqSchemas.params.properties[parameter.name] = parameter.schema; + if (!reqSchema.params) reqSchema.params = {type: "object", properties: {}}; + if (!reqSchema.params.properties) reqSchema.params.properties = {}; + reqSchema.params.properties[parameter.name] = parameter.schema; break; case "query": - if (!reqSchemas.query) reqSchemas.query = {type: "object", properties: {}}; - if (!reqSchemas.query.properties) reqSchemas.query.properties = {}; - reqSchemas.query.properties[parameter.name] = parameter.schema; + if (!reqSchema.query) reqSchema.query = {type: "object", properties: {}}; + if (!reqSchema.query.properties) reqSchema.query.properties = {}; + reqSchema.query.properties[parameter.name] = parameter.schema; break; - // case "header" + case "header": + if (!reqSchema.headers) reqSchema.headers = {type: "object", properties: {}}; + if (!reqSchema.headers.properties) reqSchema.headers.properties = {}; + reqSchema.headers.properties[parameter.name] = parameter.schema; + break; } } - const requestJsonSchema = routeDefinition.requestBody?.content?.[ContentType.json].schema; + const requestJsonSchema = routeDefinition.requestBody?.content?.[ContentType.json]?.schema; if (requestJsonSchema) { - reqSchemas.body = requestJsonSchema; + reqSchema.body = requestJsonSchema; + } + + return { + type: "object", + properties: reqSchema, + }; +} + +function buildRespSchema(routeDefinition: RouteDefinition): JsonSchema { + const respSchema: RespSchema = {}; + + const responseOk = routeDefinition.responses[StatusCode.ok]; + + // "headers": { + // "Eth-Consensus-Version": { + // "required": true, + // "schema": { + // "type": "string", + // "enum": ["phase0", "altair", "bellatrix", "capella", "deneb"], + // "example": "phase0", + // }, + // }, + // }, + + if (responseOk?.headers) { + Object.entries(responseOk.headers).map(([header, {schema}]) => { + if (!respSchema.headers) respSchema.headers = {type: "object", properties: {}}; + if (!respSchema.headers.properties) respSchema.headers.properties = {}; + respSchema.headers.properties[header] = schema; + }); + } + + const responseJsonSchema = responseOk?.content?.[ContentType.json]?.schema; + if (responseJsonSchema) { + respSchema.body = responseJsonSchema; } return { type: "object", - properties: reqSchemas as Record, + properties: respSchema, }; } @@ -213,5 +267,6 @@ function buildReqSchema(routeDefinition: RouteDefinition): JsonSchema { // - Correct URL // - Correct method // - Correct query? +// - Correct headers? // - Correct body? // - Correct return type diff --git a/packages/api/test/utils/utils.ts b/packages/api/test/utils/utils.ts index b261ae54920f..d78378e57e19 100644 --- a/packages/api/test/utils/utils.ts +++ b/packages/api/test/utils/utils.ts @@ -2,7 +2,8 @@ import {MockedObject, vi} from "vitest"; import {parse as parseQueryString} from "qs"; import {FastifyInstance, fastify} from "fastify"; import {mapValues} from "@lodestar/utils"; -import {ServerApi} from "../../src/interfaces.js"; +import {Endpoint} from "../../src/utils/index.js"; +import {ApplicationMethods, addSszContentTypeParser} from "../../src/utils/server/index.js"; export function getTestServer(): {server: FastifyInstance; start: () => Promise} { const server = fastify({ @@ -10,7 +11,9 @@ export function getTestServer(): {server: FastifyInstance; start: () => Promise< querystringParser: (str) => parseQueryString(str, {comma: true, parseArrays: false}), }); - server.addHook("onError", (request, reply, error, done) => { + addSszContentTypeParser(server); + + server.addHook("onError", (_request, _reply, error, done) => { // eslint-disable-next-line no-console console.log(`onError: ${error.toString()}`); done(); @@ -30,8 +33,8 @@ export function getTestServer(): {server: FastifyInstance; start: () => Promise< return {start, server}; } -export function getMockApi>( +export function getMockApi>( routeIds: Record -): MockedObject> & ServerApi { - return mapValues(routeIds, () => vi.fn()) as MockedObject> & ServerApi; +): MockedObject> & ApplicationMethods { + return mapValues(routeIds, () => vi.fn()) as MockedObject> & ApplicationMethods; } diff --git a/packages/beacon-node/src/api/impl/api.ts b/packages/beacon-node/src/api/impl/api.ts index d962d310b5ba..6ec7180cf0f4 100644 --- a/packages/beacon-node/src/api/impl/api.ts +++ b/packages/beacon-node/src/api/impl/api.ts @@ -1,4 +1,4 @@ -import {Api, ServerApi} from "@lodestar/api"; +import {BeaconApiMethods} from "@lodestar/api/beacon/server"; import {ApiOptions} from "../options.js"; import {ApiModules} from "./types.js"; import {getBeaconApi} from "./beacon/index.js"; @@ -11,7 +11,7 @@ import {getNodeApi} from "./node/index.js"; import {getProofApi} from "./proof/index.js"; import {getValidatorApi} from "./validator/index.js"; -export function getApi(opts: ApiOptions, modules: ApiModules): {[K in keyof Api]: ServerApi} { +export function getApi(opts: ApiOptions, modules: ApiModules): BeaconApiMethods { return { beacon: getBeaconApi(modules), config: getConfigApi(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 ed0224fc9cb6..c7c34d45971a 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -1,8 +1,8 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; -import {routes, ServerApi, ResponseFormat} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import {computeEpochAtSlot, computeTimeAtSlot, reconstructFullBlockOrContents} from "@lodestar/state-transition"; import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; -import {sleep, toHex} from "@lodestar/utils"; +import {sleep, fromHex, toHex} from "@lodestar/utils"; import {allForks, deneb, isSignedBlockContents, ProducedBlockSource} from "@lodestar/types"; import {BlockSource, getBlockInput, ImportBlockOpts, BlockInput, BlobsSource} from "../../../../chain/blocks/types.js"; import {promiseAllMaybeAsync} from "../../../../util/promises.js"; @@ -17,7 +17,7 @@ import {verifyBlocksInEpoch} from "../../../../chain/blocks/verifyBlock.js"; import {BeaconChain} from "../../../../chain/chain.js"; import {resolveBlockId, toBeaconHeaderResponse} from "./utils.js"; -type PublishBlockOpts = ImportBlockOpts & {broadcastValidation?: routes.beacon.BroadcastValidation}; +type PublishBlockOpts = ImportBlockOpts; /** * Validator clock may be advanced from beacon's clock. If the validator requests a resource in a @@ -36,9 +36,13 @@ export function getBeaconBlockApi({ metrics, network, db, -}: Pick): ServerApi { - const publishBlock: ServerApi["publishBlock"] = async ( - signedBlockOrContents, +}: Pick< + ApiModules, + "chain" | "config" | "metrics" | "network" | "db" +>): ApplicationMethods { + const publishBlock: ApplicationMethods["publishBlockV2"] = async ( + {signedBlockOrContents, broadcastValidation}, + context, opts: PublishBlockOpts = {} ) => { const seenTimestampSec = Date.now() / 1000; @@ -60,13 +64,12 @@ export function getBeaconBlockApi({ } else { signedBlock = signedBlockOrContents; blobSidecars = []; - // TODO: Once API supports submitting data as SSZ, replace null with blockBytes - blockForImport = getBlockInput.preDeneb(config, signedBlock, BlockSource.api, null); + blockForImport = getBlockInput.preDeneb(config, signedBlock, BlockSource.api, context?.sszBytes ?? null); } // check what validations have been requested before broadcasting and publishing the block // TODO: add validation time to metrics - const broadcastValidation = opts.broadcastValidation ?? routes.beacon.BroadcastValidation.gossip; + broadcastValidation = broadcastValidation ?? routes.beacon.BroadcastValidation.gossip; // if block is locally produced, full or blinded, it already is 'consensus' validated as it went through // state transition to produce the stateRoot const slot = signedBlock.message.slot; @@ -121,7 +124,7 @@ export function getBeaconBlockApi({ ); throw new BlockError(signedBlock, { code: BlockErrorCode.PARENT_UNKNOWN, - parentRoot: toHexString(signedBlock.message.parentRoot), + parentRoot: toHex(signedBlock.message.parentRoot), }); } @@ -211,8 +214,9 @@ export function getBeaconBlockApi({ await promiseAllMaybeAsync(publishPromises); }; - const publishBlindedBlock: ServerApi["publishBlindedBlock"] = async ( - signedBlindedBlock, + const publishBlindedBlock: ApplicationMethods["publishBlindedBlock"] = async ( + {signedBlindedBlock}, + context, opts: PublishBlockOpts = {} ) => { const slot = signedBlindedBlock.message.slot; @@ -236,7 +240,7 @@ export function getBeaconBlockApi({ const signedBlockOrContents = reconstructFullBlockOrContents(signedBlindedBlock, {executionPayload, contents}); chain.logger.info("Publishing assembled block", {slot, blockRoot, source}); - return publishBlock(signedBlockOrContents, opts); + return publishBlock({signedBlockOrContents}, {...context, sszBytes: null}, opts); } else { const source = ProducedBlockSource.builder; chain.logger.debug("Reconstructing signedBlockOrContents", {slot, blockRoot, source}); @@ -248,12 +252,12 @@ export function getBeaconBlockApi({ // // see: https://github.com/ChainSafe/lodestar/issues/5404 chain.logger.info("Publishing assembled block", {slot, blockRoot, source}); - return publishBlock(signedBlockOrContents, {...opts, ignoreIfKnown: true}); + return publishBlock({signedBlockOrContents}, {...context, sszBytes: null}, {...opts, ignoreIfKnown: true}); } }; return { - async getBlockHeaders(filters) { + async getBlockHeaders({slot, parentRoot}) { // TODO - SLOW CODE: This code seems like it could be improved // If one block in the response contains an optimistic block, mark the entire response as optimistic @@ -262,16 +266,15 @@ export function getBeaconBlockApi({ let finalized = true; const result: routes.beacon.BlockHeaderResponse[] = []; - if (filters.parentRoot) { - const parentRoot = filters.parentRoot; - const finalizedBlock = await db.blockArchive.getByParentRoot(fromHexString(parentRoot)); + if (parentRoot) { + const finalizedBlock = await db.blockArchive.getByParentRoot(fromHex(parentRoot)); if (finalizedBlock) { result.push(toBeaconHeaderResponse(config, finalizedBlock, true)); } const nonFinalizedBlocks = chain.forkChoice.getBlockSummariesByParentRoot(parentRoot); await Promise.all( nonFinalizedBlocks.map(async (summary) => { - const block = await db.block.get(fromHexString(summary.blockRoot)); + const block = await db.block.get(fromHex(summary.blockRoot)); if (block) { const canonical = chain.forkChoice.getCanonicalBlockAtSlot(block.message.slot); if (canonical) { @@ -286,31 +289,30 @@ export function getBeaconBlockApi({ }) ); return { - executionOptimistic, - finalized, data: result.filter( (item) => // skip if no slot filter - !(filters.slot !== undefined && filters.slot !== 0) || item.header.message.slot === filters.slot + !(slot !== undefined && slot !== 0) || item.header.message.slot === slot ), + meta: {executionOptimistic, finalized}, }; } const headSlot = chain.forkChoice.getHead().slot; - if (!filters.parentRoot && filters.slot === undefined) { - filters.slot = headSlot; + if (!parentRoot && slot === undefined) { + slot = headSlot; } - if (filters.slot !== undefined) { + if (slot !== undefined) { // future slot - if (filters.slot > headSlot) { - return {executionOptimistic: false, finalized: false, data: []}; + if (slot > headSlot) { + return {data: [], meta: {executionOptimistic: false, finalized: false}}; } - const canonicalBlock = await chain.getCanonicalBlockAtSlot(filters.slot); + const canonicalBlock = await chain.getCanonicalBlockAtSlot(slot); // skip slot if (!canonicalBlock) { - return {executionOptimistic: false, finalized: false, data: []}; + return {data: [], meta: {executionOptimistic: false, finalized: false}}; } const canonicalRoot = config .getForkTypes(canonicalBlock.block.message.slot) @@ -323,14 +325,14 @@ export function getBeaconBlockApi({ // fork blocks // TODO: What is this logic? await Promise.all( - chain.forkChoice.getBlockSummariesAtSlot(filters.slot).map(async (summary) => { + chain.forkChoice.getBlockSummariesAtSlot(slot).map(async (summary) => { if (isOptimisticBlock(summary)) { executionOptimistic = true; } finalized = false; - if (summary.blockRoot !== toHexString(canonicalRoot)) { - const block = await db.block.get(fromHexString(summary.blockRoot)); + if (summary.blockRoot !== toHex(canonicalRoot)) { + const block = await db.block.get(fromHex(summary.blockRoot)); if (block) { result.push(toBeaconHeaderResponse(config, block)); } @@ -340,54 +342,45 @@ export function getBeaconBlockApi({ } return { - executionOptimistic, - finalized, data: result, + meta: {executionOptimistic, finalized}, }; }, - async getBlockHeader(blockId) { + async getBlockHeader({blockId}) { const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); return { - executionOptimistic, - finalized, data: toBeaconHeaderResponse(config, block, true), + meta: {executionOptimistic, finalized}, }; }, - async getBlock(blockId, format?: ResponseFormat) { + async getBlock({blockId}) { const {block} = await resolveBlockId(chain, blockId); - if (format === "ssz") { - return config.getForkTypes(block.message.slot).SignedBeaconBlock.serialize(block); - } - return { - data: block, - }; + return {data: block}; }, - async getBlockV2(blockId, format?: ResponseFormat) { + async getBlockV2({blockId}) { const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); - if (format === "ssz") { - return config.getForkTypes(block.message.slot).SignedBeaconBlock.serialize(block); - } return { - executionOptimistic, - finalized, data: block, - version: config.getForkName(block.message.slot), + meta: { + executionOptimistic, + finalized, + version: config.getForkName(block.message.slot), + }, }; }, - async getBlockAttestations(blockId) { + async getBlockAttestations({blockId}) { const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); return { - executionOptimistic, - finalized, data: Array.from(block.message.body.attestations), + meta: {executionOptimistic, finalized}, }; }, - async getBlockRoot(blockId) { + async getBlockRoot({blockId}) { // Fast path: From head state already available in memory get historical blockRoot const slot = typeof blockId === "string" ? parseInt(blockId) : blockId; if (!Number.isNaN(slot)) { @@ -395,50 +388,49 @@ export function getBeaconBlockApi({ if (slot === head.slot) { return { - executionOptimistic: isOptimisticBlock(head), - finalized: false, - data: {root: fromHexString(head.blockRoot)}, + data: {root: fromHex(head.blockRoot)}, + meta: {executionOptimistic: isOptimisticBlock(head), finalized: false}, }; } if (slot < head.slot && head.slot <= slot + SLOTS_PER_HISTORICAL_ROOT) { const state = chain.getHeadState(); return { - executionOptimistic: isOptimisticBlock(head), - finalized: computeEpochAtSlot(slot) <= chain.forkChoice.getFinalizedCheckpoint().epoch, data: {root: state.blockRoots.get(slot % SLOTS_PER_HISTORICAL_ROOT)}, + meta: { + executionOptimistic: isOptimisticBlock(head), + finalized: computeEpochAtSlot(slot) <= chain.forkChoice.getFinalizedCheckpoint().epoch, + }, }; } } else if (blockId === "head") { const head = chain.forkChoice.getHead(); return { - executionOptimistic: isOptimisticBlock(head), - finalized: false, - data: {root: fromHexString(head.blockRoot)}, + data: {root: fromHex(head.blockRoot)}, + meta: {executionOptimistic: isOptimisticBlock(head), finalized: false}, }; } // Slow path const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); return { - executionOptimistic, - finalized, data: {root: config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message)}, + meta: {executionOptimistic, finalized}, }; }, publishBlock, publishBlindedBlock, - async publishBlindedBlockV2(signedBlindedBlockOrContents, opts) { - await publishBlindedBlock(signedBlindedBlockOrContents, opts); + async publishBlindedBlockV2(args, context, opts) { + await publishBlindedBlock(args, context, opts); }, - async publishBlockV2(signedBlockOrContents, opts) { - await publishBlock(signedBlockOrContents, opts); + async publishBlockV2(args, context, opts) { + await publishBlock(args, context, opts); }, - async getBlobSidecars(blockId, indices) { + async getBlobSidecars({blockId, indices}) { const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); const blockRoot = config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message); @@ -448,13 +440,16 @@ export function getBeaconBlockApi({ } if (!blobSidecars) { - throw Error(`blobSidecars not found in db for slot=${block.message.slot} root=${toHexString(blockRoot)}`); + throw Error(`blobSidecars not found in db for slot=${block.message.slot} root=${toHex(blockRoot)}`); } return { - executionOptimistic, - finalized, data: indices ? blobSidecars.filter(({index}) => indices.includes(index)) : blobSidecars, + meta: { + executionOptimistic, + finalized, + version: config.getForkName(block.message.slot), + }, }; }, }; @@ -466,7 +461,7 @@ async function reconstructBuilderBlockOrContents( ): Promise { const executionBuilder = chain.executionBuilder; if (!executionBuilder) { - throw Error("exeutionBuilder required to publish SignedBlindedBeaconBlock"); + throw Error("executionBuilder required to publish SignedBlindedBeaconBlock"); } const signedBlockOrContents = await executionBuilder.submitBlindedBlock(signedBlindedBlock); diff --git a/packages/beacon-node/src/api/impl/beacon/index.ts b/packages/beacon-node/src/api/impl/beacon/index.ts index 492e2f8ff8b1..2e75dc76782c 100644 --- a/packages/beacon-node/src/api/impl/beacon/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/index.ts @@ -1,4 +1,5 @@ -import {routes, ServerApi} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import {ApiModules} from "../types.js"; import {getBeaconBlockApi} from "./blocks/index.js"; import {getBeaconPoolApi} from "./pool/index.js"; @@ -7,7 +8,7 @@ import {getBeaconRewardsApi} from "./rewards/index.js"; export function getBeaconApi( modules: Pick -): ServerApi { +): ApplicationMethods { const block = getBeaconBlockApi(modules); const pool = getBeaconPoolApi(modules); const state = getBeaconStateApi(modules); diff --git a/packages/beacon-node/src/api/impl/beacon/pool/index.ts b/packages/beacon-node/src/api/impl/beacon/pool/index.ts index 09a66eba4d15..8372b84db3b1 100644 --- a/packages/beacon-node/src/api/impl/beacon/pool/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/pool/index.ts @@ -1,4 +1,5 @@ -import {routes, ServerApi} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import {Epoch, ssz} from "@lodestar/types"; import {SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params"; import {validateApiAttestation} from "../../../../chain/validation/index.js"; @@ -21,14 +22,14 @@ export function getBeaconPoolApi({ logger, metrics, network, -}: Pick): ServerApi { +}: Pick): ApplicationMethods { return { - async getPoolAttestations(filters) { + async getPoolAttestations({slot, committeeIndex}) { // Already filtered by slot - let attestations = chain.aggregatedAttestationPool.getAll(filters?.slot); + let attestations = chain.aggregatedAttestationPool.getAll(slot); - if (filters?.committeeIndex !== undefined) { - attestations = attestations.filter((attestation) => filters.committeeIndex === attestation.data.index); + if (committeeIndex !== undefined) { + attestations = attestations.filter((attestation) => committeeIndex === attestation.data.index); } return {data: attestations}; @@ -46,16 +47,16 @@ export function getBeaconPoolApi({ return {data: chain.opPool.getAllVoluntaryExits()}; }, - async getPoolBlsToExecutionChanges() { + async getPoolBLSToExecutionChanges() { return {data: chain.opPool.getAllBlsToExecutionChanges().map(({data}) => data)}; }, - async submitPoolAttestations(attestations) { + async submitPoolAttestations({signedAttestations}) { const seenTimestampSec = Date.now() / 1000; const errors: Error[] = []; await Promise.all( - attestations.map(async (attestation, i) => { + signedAttestations.map(async (attestation, i) => { try { const fork = chain.config.getForkName(chain.clock.currentSlot); // eslint-disable-next-line @typescript-eslint/explicit-function-return-type @@ -107,26 +108,26 @@ export function getBeaconPoolApi({ } }, - async submitPoolAttesterSlashings(attesterSlashing) { + async submitPoolAttesterSlashings({attesterSlashing}) { await validateApiAttesterSlashing(chain, attesterSlashing); chain.opPool.insertAttesterSlashing(attesterSlashing); await network.publishAttesterSlashing(attesterSlashing); }, - async submitPoolProposerSlashings(proposerSlashing) { + async submitPoolProposerSlashings({proposerSlashing}) { await validateApiProposerSlashing(chain, proposerSlashing); chain.opPool.insertProposerSlashing(proposerSlashing); await network.publishProposerSlashing(proposerSlashing); }, - async submitPoolVoluntaryExit(voluntaryExit) { - await validateApiVoluntaryExit(chain, voluntaryExit); - chain.opPool.insertVoluntaryExit(voluntaryExit); - chain.emitter.emit(routes.events.EventType.voluntaryExit, voluntaryExit); - await network.publishVoluntaryExit(voluntaryExit); + async submitPoolVoluntaryExit({signedVoluntaryExit}) { + await validateApiVoluntaryExit(chain, signedVoluntaryExit); + chain.opPool.insertVoluntaryExit(signedVoluntaryExit); + chain.emitter.emit(routes.events.EventType.voluntaryExit, signedVoluntaryExit); + await network.publishVoluntaryExit(signedVoluntaryExit); }, - async submitPoolBlsToExecutionChange(blsToExecutionChanges) { + async submitPoolBLSToExecutionChange({blsToExecutionChanges}) { const errors: Error[] = []; await Promise.all( @@ -145,7 +146,7 @@ export function getBeaconPoolApi({ } catch (e) { errors.push(e as Error); logger.error( - `Error on submitPoolBlsToExecutionChange [${i}]`, + `Error on submitPoolBLSToExecutionChange [${i}]`, {validatorIndex: blsToExecutionChange.message.validatorIndex}, e as Error ); @@ -154,7 +155,7 @@ export function getBeaconPoolApi({ ); if (errors.length > 1) { - throw Error("Multiple errors on submitPoolBlsToExecutionChange\n" + errors.map((e) => e.message).join("\n")); + throw Error("Multiple errors on submitPoolBLSToExecutionChange\n" + errors.map((e) => e.message).join("\n")); } else if (errors.length === 1) { throw errors[0]; } @@ -170,7 +171,7 @@ export function getBeaconPoolApi({ * * https://github.com/ethereum/beacon-APIs/pull/135 */ - async submitPoolSyncCommitteeSignatures(signatures) { + async submitPoolSyncCommitteeSignatures({signatures}) { // Fetch states for all slots of the `signatures` const slots = new Set(); for (const signature of signatures) { 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 f1c7d3eb6b8e..96399db27b4f 100644 --- a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts @@ -1,22 +1,25 @@ -import {routes, ServerApi} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import {ApiModules} from "../../types.js"; import {resolveBlockId} from "../blocks/utils.js"; -export function getBeaconRewardsApi({chain}: Pick): ServerApi { +export function getBeaconRewardsApi({ + chain, +}: Pick): ApplicationMethods { return { - async getBlockRewards(blockId) { + async getBlockRewards({blockId}) { const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); const data = await chain.getBlockRewards(block.message); - return {data, executionOptimistic, finalized}; + return {data, meta: {executionOptimistic, finalized}}; }, - async getAttestationsRewards(epoch, validatorIds) { + async getAttestationsRewards({epoch, validatorIds}) { const {rewards, executionOptimistic, finalized} = await chain.getAttestationsRewards(epoch, validatorIds); - return {data: rewards, executionOptimistic, finalized}; + return {data: rewards, meta: {executionOptimistic, finalized}}; }, - async getSyncCommitteeRewards(blockId, validatorIds) { + async getSyncCommitteeRewards({blockId, validatorIds}) { const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); const data = await chain.getSyncCommitteeRewards(block.message, validatorIds); - return {data, executionOptimistic, finalized}; + 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 d61d36bf83b6..e5592d461ff7 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/index.ts @@ -1,4 +1,5 @@ -import {routes, ServerApi} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import { BeaconStateAllForks, CachedBeaconStateAltair, @@ -21,7 +22,7 @@ import { export function getBeaconStateApi({ chain, config, -}: Pick): ServerApi { +}: Pick): ApplicationMethods { async function getState( stateId: routes.beacon.StateId ): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean}> { @@ -29,25 +30,23 @@ export function getBeaconStateApi({ } return { - async getStateRoot(stateId) { + async getStateRoot({stateId}) { const {state, executionOptimistic, finalized} = await getState(stateId); return { - executionOptimistic, - finalized, data: {root: state.hashTreeRoot()}, + meta: {executionOptimistic, finalized}, }; }, - async getStateFork(stateId) { + async getStateFork({stateId}) { const {state, executionOptimistic, finalized} = await getState(stateId); return { - executionOptimistic, - finalized, data: state.fork, + meta: {executionOptimistic, finalized}, }; }, - async getStateRandao(stateId, epoch) { + async getStateRandao({stateId, epoch}) { const {state, executionOptimistic, finalized} = await getState(stateId); const stateEpoch = computeEpochAtSlot(state.slot); const usedEpoch = epoch ?? stateEpoch; @@ -59,41 +58,37 @@ export function getBeaconStateApi({ const randao = getRandaoMix(state, usedEpoch); return { - executionOptimistic, - finalized, - data: { - randao, - }, + data: {randao}, + meta: {executionOptimistic, finalized}, }; }, - async getStateFinalityCheckpoints(stateId) { + async getStateFinalityCheckpoints({stateId}) { const {state, executionOptimistic, finalized} = await getState(stateId); return { - executionOptimistic, - finalized, data: { currentJustified: state.currentJustifiedCheckpoint, previousJustified: state.previousJustifiedCheckpoint, finalized: state.finalizedCheckpoint, }, + meta: {executionOptimistic, finalized}, }; }, - async getStateValidators(stateId, filters) { + async getStateValidators({stateId, validatorIds, statuses}) { const {state, executionOptimistic, finalized} = await resolveStateId(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; const validatorResponses: routes.beacon.ValidatorResponse[] = []; - if (filters?.id) { - for (const id of filters.id) { + if (validatorIds) { + for (const id of validatorIds) { const resp = getStateValidatorIndex(id, state, pubkey2index); if (resp.valid) { const validatorIndex = resp.validatorIndex; const validator = validators.getReadonly(validatorIndex); - if (filters.status && !filters.status.includes(getValidatorStatus(validator, currentEpoch))) { + if (statuses && !statuses.includes(getValidatorStatus(validator, currentEpoch))) { continue; } const validatorResponse = toValidatorResponse( @@ -106,16 +101,14 @@ export function getBeaconStateApi({ } } return { - executionOptimistic, - finalized, data: validatorResponses, + meta: {executionOptimistic, finalized}, }; - } else if (filters?.status) { - const validatorsByStatus = filterStateValidatorsByStatus(filters.status, state, pubkey2index, currentEpoch); + } else if (statuses) { + const validatorsByStatus = filterStateValidatorsByStatus(statuses, state, pubkey2index, currentEpoch); return { - executionOptimistic, - finalized, data: validatorsByStatus, + meta: {executionOptimistic, finalized}, }; } @@ -128,17 +121,16 @@ export function getBeaconStateApi({ } return { - executionOptimistic, - finalized, data: resp, + meta: {executionOptimistic, finalized}, }; }, - async postStateValidators(stateId, filters) { - return this.getStateValidators(stateId, filters); + async postStateValidators(args, context) { + return this.getStateValidators(args, context); }, - async getStateValidator(stateId, validatorId) { + async getStateValidator({stateId, validatorId}) { const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); const {pubkey2index} = chain.getHeadState().epochCtx; @@ -149,24 +141,23 @@ export function getBeaconStateApi({ const validatorIndex = resp.validatorIndex; return { - executionOptimistic, - finalized, data: toValidatorResponse( validatorIndex, state.validators.getReadonly(validatorIndex), state.balances.get(validatorIndex), getCurrentEpoch(state) ), + meta: {executionOptimistic, finalized}, }; }, - async getStateValidatorBalances(stateId, indices) { + async getStateValidatorBalances({stateId, validatorIds}) { const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); - if (indices) { + if (validatorIds) { const headState = chain.getHeadState(); const balances: routes.beacon.ValidatorBalance[] = []; - for (const id of indices) { + for (const id of validatorIds) { if (typeof id === "number") { if (state.validators.length <= id) { continue; @@ -180,9 +171,8 @@ export function getBeaconStateApi({ } } return { - executionOptimistic, - finalized, data: balances, + meta: {executionOptimistic, finalized}, }; } @@ -193,17 +183,16 @@ export function getBeaconStateApi({ resp.push({index: i, balance: balancesArr[i]}); } return { - executionOptimistic, - finalized, data: resp, + meta: {executionOptimistic, finalized}, }; }, - async postStateValidatorBalances(stateId, indices) { - return this.getStateValidatorBalances(stateId, indices); + async postStateValidatorBalances(args, context) { + return this.getStateValidatorBalances(args, context); }, - async getEpochCommittees(stateId, filters) { + async getEpochCommittees({stateId, ...filters}) { const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); const stateCached = state as CachedBeaconStateAltair; @@ -211,33 +200,32 @@ export function getBeaconStateApi({ throw new ApiError(400, `No cached state available for stateId: ${stateId}`); } - const epoch = filters?.epoch ?? computeEpochAtSlot(state.slot); + const epoch = filters.epoch ?? computeEpochAtSlot(state.slot); const startSlot = computeStartSlotAtEpoch(epoch); const shuffling = stateCached.epochCtx.getShufflingAtEpoch(epoch); const committees = shuffling.committees; const committeesFlat = committees.flatMap((slotCommittees, slotInEpoch) => { const slot = startSlot + slotInEpoch; - if (filters?.slot !== undefined && filters.slot !== slot) { + if (filters.slot !== undefined && filters.slot !== slot) { return []; } return slotCommittees.flatMap((committee, committeeIndex) => { - if (filters?.index !== undefined && filters.index !== committeeIndex) { + if (filters.index !== undefined && filters.index !== committeeIndex) { return []; } return [ { index: committeeIndex, slot, - validators: committee, + validators: Array.from(committee), }, ]; }); }); return { - executionOptimistic, - finalized, data: committeesFlat, + meta: {executionOptimistic, finalized}, }; }, @@ -245,7 +233,7 @@ export function getBeaconStateApi({ * Retrieves the sync committees for the given state. * @param epoch Fetch sync committees for the given epoch. If not present then the sync committees for the epoch of the state will be obtained. */ - async getEpochSyncCommittees(stateId, epoch) { + async getEpochSyncCommittees({stateId, epoch}) { // TODO: Should pick a state with the provided epoch too const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); @@ -264,13 +252,12 @@ export function getBeaconStateApi({ const syncCommitteeCache = stateCached.epochCtx.getIndexedSyncCommitteeAtEpoch(epoch ?? stateEpoch); return { - executionOptimistic, - finalized, data: { validators: syncCommitteeCache.validatorIndices, // TODO: This is not used by the validator and will be deprecated soon validatorAggregates: [], }, + meta: {executionOptimistic, finalized}, }; }, }; 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 5ed624e9eedf..73f7134e1530 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/utils.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/utils.ts @@ -1,9 +1,8 @@ -import {fromHexString} from "@chainsafe/ssz"; import {routes} from "@lodestar/api"; import {FAR_FUTURE_EPOCH, GENESIS_SLOT} from "@lodestar/params"; import {BeaconStateAllForks, PubkeyIndexMap} from "@lodestar/state-transition"; -import {BLSPubkey, phase0} from "@lodestar/types"; -import {Epoch, ValidatorIndex} from "@lodestar/types"; +import {BLSPubkey, Epoch, phase0, ValidatorIndex} from "@lodestar/types"; +import {fromHex} from "@lodestar/utils"; import {IBeaconChain, StateGetOpts} from "../../../../chain/index.js"; import {ApiError, ValidationError} from "../../errors.js"; import {isOptimisticBlock} from "../../../../util/forkChoice.js"; @@ -142,7 +141,7 @@ export function getStateValidatorIndex( // mutate `id` and fallthrough to below if (id.startsWith("0x")) { try { - id = fromHexString(id); + id = fromHex(id); } catch (e) { return {valid: false, code: 400, reason: "Invalid pubkey hex encoding"}; } diff --git a/packages/beacon-node/src/api/impl/config/index.ts b/packages/beacon-node/src/api/impl/config/index.ts index 209906fd415c..9b8694aa5914 100644 --- a/packages/beacon-node/src/api/impl/config/index.ts +++ b/packages/beacon-node/src/api/impl/config/index.ts @@ -1,4 +1,5 @@ -import {routes, ServerApi} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import {chainConfigToJson, ChainConfig, specValuesToJson} from "@lodestar/config"; import {activePreset, presetToJson} from "@lodestar/params"; import {ApiModules} from "../types.js"; @@ -21,7 +22,7 @@ export function renderJsonSpec(config: ChainConfig): Record { return {...configJson, ...presetJson, ...constantsJson}; } -export function getConfigApi({config}: Pick): ServerApi { +export function getConfigApi({config}: Pick): ApplicationMethods { return { async getForkSchedule() { const forkInfos = Object.values(config.forks); diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index a03c00bd7150..c5c0eeda03a3 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -1,9 +1,14 @@ -import {routes, ServerApi, ResponseFormat} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; +import {phase0} from "@lodestar/types"; import {resolveStateId} from "../beacon/state/utils.js"; import {ApiModules} from "../types.js"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; -export function getDebugApi({chain, config}: Pick): ServerApi { +export function getDebugApi({ + chain, + config, +}: Pick): ApplicationMethods { return { async getDebugChainHeads() { const heads = chain.forkChoice.getHeads(); @@ -36,22 +41,24 @@ export function getDebugApi({chain, config}: Pick): ServerApi { +export function getEventsApi({ + chain, +}: Pick): ApplicationMethods { return { - async eventstream(topics, signal, onEvent) { + async eventstream({topics, signal, onEvent}) { const onAbortFns: (() => void)[] = []; for (const topic of topics) { diff --git a/packages/beacon-node/src/api/impl/lightclient/index.ts b/packages/beacon-node/src/api/impl/lightclient/index.ts index 83052630f343..13deb16a9cb0 100644 --- a/packages/beacon-node/src/api/impl/lightclient/index.ts +++ b/packages/beacon-node/src/api/impl/lightclient/index.ts @@ -1,6 +1,6 @@ -import {fromHexString} from "@chainsafe/ssz"; -import {routes, ServerApi} from "@lodestar/api"; -import {SyncPeriod} from "@lodestar/types"; +import {fromHex} from "@lodestar/utils"; +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"; @@ -9,40 +9,40 @@ import {ApiModules} from "../types.js"; export function getLightclientApi({ chain, config, -}: Pick): ServerApi { +}: Pick): ApplicationMethods { return { - async getUpdates(startPeriod: SyncPeriod, count: number) { + async getLightClientUpdatesByRange({startPeriod, count}) { 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))); - return updates.map((update) => ({ - version: config.getForkName(update.attestedHeader.beacon.slot), - data: update, - })); + return { + data: updates, + meta: {versions: updates.map((update) => config.getForkName(update.attestedHeader.beacon.slot))}, + }; }, - async getOptimisticUpdate() { - const data = chain.lightClientServer.getOptimisticUpdate(); - if (data === null) { + async getLightClientOptimisticUpdate() { + const update = chain.lightClientServer.getOptimisticUpdate(); + if (update === null) { throw Error("No optimistic update available"); } - return {version: config.getForkName(data.attestedHeader.beacon.slot), data}; + return {data: update, meta: {version: config.getForkName(update.attestedHeader.beacon.slot)}}; }, - async getFinalityUpdate() { - const data = chain.lightClientServer.getFinalityUpdate(); - if (data === null) { + async getLightClientFinalityUpdate() { + const update = chain.lightClientServer.getFinalityUpdate(); + if (update === null) { throw Error("No finality update available"); } - return {version: config.getForkName(data.attestedHeader.beacon.slot), data}; + return {data: update, meta: {version: config.getForkName(update.attestedHeader.beacon.slot)}}; }, - async getBootstrap(blockRoot) { - const bootstrapProof = await chain.lightClientServer.getBootstrap(fromHexString(blockRoot)); - return {version: config.getForkName(bootstrapProof.header.beacon.slot), data: bootstrapProof}; + async getLightClientBootstrap({blockRoot}) { + const bootstrapProof = await chain.lightClientServer.getBootstrap(fromHex(blockRoot)); + return {data: bootstrapProof, meta: {version: config.getForkName(bootstrapProof.header.beacon.slot)}}; }, - async getCommitteeRoot(startPeriod: SyncPeriod, count: number) { + async getLightClientCommitteeRoot({startPeriod, count}) { 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( diff --git a/packages/beacon-node/src/api/impl/lodestar/index.ts b/packages/beacon-node/src/api/impl/lodestar/index.ts index 8048e668662b..d3083dda8e9c 100644 --- a/packages/beacon-node/src/api/impl/lodestar/index.ts +++ b/packages/beacon-node/src/api/impl/lodestar/index.ts @@ -1,13 +1,12 @@ import fs from "node:fs"; import path from "node:path"; -import {toHexString} from "@chainsafe/ssz"; -import {routes, ServerApi} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import {Repository} from "@lodestar/db"; import {toHex} from "@lodestar/utils"; import {getLatestWeakSubjectivityCheckpointEpoch} from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; import {ssz} from "@lodestar/types"; -import {LodestarThreadType} from "@lodestar/api/lib/beacon/routes/lodestar.js"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {BeaconChain} from "../../../chain/index.js"; import {QueuedStateRegenerator, RegenRequest} from "../../../chain/regen/index.js"; @@ -22,13 +21,13 @@ export function getLodestarApi({ db, network, sync, -}: Pick): ServerApi { +}: Pick): ApplicationMethods { let writingHeapdump = false; let writingProfile = false; const defaultProfileMs = SLOTS_PER_EPOCH * config.SECONDS_PER_SLOT * 1000; return { - async writeHeapdump(thread = "main", dirpath = ".") { + async writeHeapdump({thread = "main", dirpath = "."}) { if (writingHeapdump) { throw Error("Already writing heapdump"); } @@ -54,7 +53,7 @@ export function getLodestarApi({ } }, - async writeProfile(thread: LodestarThreadType = "network", durationMs = defaultProfileMs, dirpath = ".") { + async writeProfile({thread = "network", duration = defaultProfileMs, dirpath = "."}) { if (writingProfile) { throw Error("Already writing network profile"); } @@ -65,14 +64,14 @@ export function getLodestarApi({ let profile: string; switch (thread) { case "network": - filepath = await network.writeNetworkThreadProfile(durationMs, dirpath); + filepath = await network.writeNetworkThreadProfile(duration, dirpath); break; case "discv5": - filepath = await network.writeDiscv5Profile(durationMs, dirpath); + filepath = await network.writeDiscv5Profile(duration, dirpath); break; default: // main thread - profile = await profileNodeJS(durationMs); + profile = await profileNodeJS(duration); filepath = path.join(dirpath, `main_thread_${new Date().toISOString()}.cpuprofile`); fs.writeFileSync(filepath, profile); break; @@ -92,7 +91,7 @@ export function getLodestarApi({ return {data: sync.getSyncChainsDebugState()}; }, - async getGossipQueueItems(gossipType: GossipType | string) { + async getGossipQueueItems({gossipType}) { return { data: await network.dumpGossipQueue(gossipType as GossipType), }; @@ -144,16 +143,15 @@ export function getLodestarApi({ chain.regen.dropCache(); }, - async connectPeer(peerIdStr, multiaddrStrs) { - await network.connectToPeer(peerIdStr, multiaddrStrs); + async connectPeer({peerId, multiaddrs}) { + await network.connectToPeer(peerId, multiaddrs); }, - async disconnectPeer(peerIdStr) { - await network.disconnectPeer(peerIdStr); + async disconnectPeer({peerId}) { + await network.disconnectPeer(peerId); }, - async getPeers(filters) { - const {state, direction} = filters || {}; + async getPeers({state, direction}) { const peers = (await network.dumpPeers()).filter( (nodePeer) => (!state || state.length === 0 || state.includes(nodePeer.state)) && @@ -172,16 +170,16 @@ export function getLodestarApi({ }; }, - async dumpDbBucketKeys(bucketReq) { + async dumpDbBucketKeys({bucket}) { for (const repo of Object.values(db) as IBeaconDb[keyof IBeaconDb][]) { if (repo instanceof Repository) { - if (String(repo["bucket"]) === bucketReq || repo["bucketId"] === bucketReq) { + if (String(repo["bucket"]) === bucket || repo["bucketId"] === bucket) { return {data: stringifyKeys(await repo.keys())}; } } } - throw Error(`Unknown Bucket '${bucketReq}'`); + throw Error(`Unknown Bucket '${bucket}'`); }, async dumpDbStateIndex() { @@ -204,7 +202,7 @@ function regenRequestToJson(config: ChainForkConfig, regenRequest: RegenRequest) case "getPreState": { const slot = regenRequest.args[0].slot; return { - root: toHexString(config.getForkTypes(slot).BeaconBlock.hashTreeRoot(regenRequest.args[0])), + root: toHex(config.getForkTypes(slot).BeaconBlock.hashTreeRoot(regenRequest.args[0])), slot, }; } diff --git a/packages/beacon-node/src/api/impl/node/index.ts b/packages/beacon-node/src/api/impl/node/index.ts index 26b7f827d1fd..103552ffb5b9 100644 --- a/packages/beacon-node/src/api/impl/node/index.ts +++ b/packages/beacon-node/src/api/impl/node/index.ts @@ -1,4 +1,5 @@ -import {routes, ServerApi} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import {ApiError} from "../errors.js"; import {ApiModules} from "../types.js"; import {ApiOptions} from "../../options.js"; @@ -6,7 +7,7 @@ import {ApiOptions} from "../../options.js"; export function getNodeApi( opts: ApiOptions, {network, sync}: Pick -): ServerApi { +): ApplicationMethods { return { async getNetworkIdentity() { return { @@ -14,16 +15,15 @@ export function getNodeApi( }; }, - async getPeer(peerIdStr) { - const peer = await network.dumpPeer(peerIdStr); + async getPeer({peerId}) { + const peer = await network.dumpPeer(peerId); if (!peer) { throw new ApiError(404, "Node has not seen this peer"); } return {data: peer}; }, - async getPeers(filters) { - const {state, direction} = filters || {}; + async getPeers({state, direction}) { const peers = (await network.dumpPeers()).filter( (nodePeer) => (!state || state.length === 0 || state.includes(nodePeer.state)) && @@ -66,29 +66,22 @@ export function getNodeApi( return {data: sync.getSyncStatus()}; }, - async getHealth(options) { - const syncingStatus = options?.syncingStatus; - + async getHealth({syncingStatus}) { if (syncingStatus != null && (syncingStatus < 100 || syncingStatus > 599)) { throw new ApiError(400, `Invalid syncing status code: ${syncingStatus}`); } - let healthStatus: number; - if (sync.getSyncStatus().isSyncing) { // 206: Node is syncing but can serve incomplete data - healthStatus = syncingStatus ?? routes.node.NodeHealth.SYNCING; + return {status: syncingStatus ?? routes.node.NodeHealth.SYNCING}; } else { // 200: Node is ready - healthStatus = routes.node.NodeHealth.READY; + return {status: routes.node.NodeHealth.READY}; } // else { // 503: Node not initialized or having issues // NOTE: Lodestar does not start its API until fully initialized, so this status can never be served // } - - // Health status is returned to allow route handler to set it as HTTP status code - return healthStatus as unknown as void; }, }; } diff --git a/packages/beacon-node/src/api/impl/proof/index.ts b/packages/beacon-node/src/api/impl/proof/index.ts index 09f691c4c453..a581fb8eed59 100644 --- a/packages/beacon-node/src/api/impl/proof/index.ts +++ b/packages/beacon-node/src/api/impl/proof/index.ts @@ -1,5 +1,6 @@ -import {createProof, ProofType} from "@chainsafe/persistent-merkle-tree"; -import {routes, ServerApi} from "@lodestar/api"; +import {CompactMultiProof, createProof, ProofType} from "@chainsafe/persistent-merkle-tree"; +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"; @@ -8,13 +9,13 @@ import {ApiOptions} from "../../options.js"; export function getProofApi( opts: ApiOptions, {chain, config}: Pick -): ServerApi { +): ApplicationMethods { // It's currently possible to request gigantic proofs (eg: a proof of the entire beacon state) // We want some some sort of resistance against this DoS vector. const maxGindicesInProof = opts.maxGindicesInProof ?? 512; return { - async getStateProof(stateId, descriptor) { + async getStateProof({stateId, descriptor}) { // descriptor.length / 2 is a rough approximation of # of gindices if (descriptor.length / 2 > maxGindicesInProof) { throw new Error("Requested proof is too large."); @@ -26,11 +27,14 @@ export function getProofApi( state.commit(); const stateNode = state.node; - const data = createProof(stateNode, {type: ProofType.compactMulti, descriptor}); + const proof = createProof(stateNode, {type: ProofType.compactMulti, descriptor}); - return {data}; + return { + data: proof as CompactMultiProof, + meta: {version: config.getForkName(state.slot)}, + }; }, - async getBlockProof(blockId, descriptor) { + async getBlockProof({blockId, descriptor}) { // descriptor.length / 2 is a rough approximation of # of gindices if (descriptor.length / 2 > maxGindicesInProof) { throw new Error("Requested proof is too large."); @@ -41,9 +45,12 @@ export function getProofApi( // 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; - const data = createProof(blockNode, {type: ProofType.compactMulti, descriptor}); + const proof = createProof(blockNode, {type: ProofType.compactMulti, descriptor}); - return {data}; + return { + data: proof as CompactMultiProof, + meta: {version: config.getForkName(block.message.slot)}, + }; }, }; } diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 5a10b8337e9c..0b8f4d9cce36 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -1,5 +1,5 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; -import {routes, ServerApi} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import { CachedBeaconStateAllForks, computeStartSlotAtEpoch, @@ -18,6 +18,9 @@ import { isForkBlobs, isForkExecution, ForkSeq, + ForkPreBlobs, + ForkBlobs, + ForkExecution, } from "@lodestar/params"; import {MAX_BUILDER_BOOST_FACTOR} from "@lodestar/validator"; import { @@ -33,9 +36,10 @@ import { isBlindedBeaconBlock, isBlockContents, phase0, + Wei, } from "@lodestar/types"; import {ExecutionStatus} from "@lodestar/fork-choice"; -import {toHex, resolveOrRacePromises, prettyWeiToEth} from "@lodestar/utils"; +import {fromHex, toHex, resolveOrRacePromises, prettyWeiToEth} from "@lodestar/utils"; import { AttestationError, AttestationErrorCode, @@ -86,6 +90,20 @@ const BLOCK_PRODUCTION_RACE_CUTOFF_MS = 2_000; /** Overall timeout for execution and block production apis */ const BLOCK_PRODUCTION_RACE_TIMEOUT_MS = 12_000; +type ProduceBlockOrContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & ( + | {data: allForks.BeaconBlock; version: ForkPreBlobs} + | {data: allForks.BlockContents; version: ForkBlobs} +); +type ProduceBlindedBlockRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & { + data: allForks.BlindedBeaconBlock; + version: ForkExecution; +}; + +type ProduceFullOrBlindedBlockOrContentsRes = {executionPayloadSource: ProducedBlockSource} & ( + | (ProduceBlockOrContentsRes & {executionPayloadBlinded: false}) + | (ProduceBlindedBlockRes & {executionPayloadBlinded: true}) +); + /** * Server implementation for handling validator duties. * See `@lodestar/validator/src/api` for the client implementation). @@ -97,7 +115,7 @@ export function getValidatorApi({ metrics, network, sync, -}: ApiModules): ServerApi { +}: ApiModules): ApplicationMethods { let genesisBlockRoot: Root | null = null; /** @@ -225,7 +243,7 @@ export function getValidatorApi({ } const cp = { epoch: cpHex.epoch, - root: fromHexString(cpHex.rootHex), + root: fromHex(cpHex.rootHex), }; const slot0 = computeStartSlotAtEpoch(cp.epoch); // if not, wait for ChainEvent.checkpoint event until slot 1 of epoch @@ -315,7 +333,7 @@ export function getValidatorApi({ ); } - const produceBuilderBlindedBlock = async function produceBuilderBlindedBlock( + async function produceBuilderBlindedBlock( slot: Slot, randaoReveal: BLSSignature, graffiti: string, @@ -324,7 +342,7 @@ export function getValidatorApi({ skipHeadChecksAndUpdate, commonBlockBody, parentBlockRoot: inParentBlockRoot, - }: Omit & + }: Omit & ( | { skipHeadChecksAndUpdate: true; @@ -337,7 +355,7 @@ export function getValidatorApi({ parentBlockRoot?: undefined; } ) = {} - ): Promise { + ): Promise { const version = config.getForkName(slot); if (!isForkExecution(version)) { throw Error(`Invalid fork=${version} for produceBuilderBlindedBlock`); @@ -363,7 +381,7 @@ export function getValidatorApi({ // forkChoice.updateTime() might have already been called by the onSlot clock // handler, in which case this should just return. chain.forkChoice.updateTime(slot); - parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot); + parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot); } else { parentBlockRoot = inParentBlockRoot; } @@ -385,7 +403,7 @@ export function getValidatorApi({ slot, executionPayloadValue, consensusBlockValue, - root: toHexString(config.getBlindedForkTypes(slot).BeaconBlock.hashTreeRoot(block)), + root: toHex(config.getBlindedForkTypes(slot).BeaconBlock.hashTreeRoot(block)), }); if (chain.opts.persistProducedBlocks) { @@ -396,9 +414,9 @@ export function getValidatorApi({ } finally { if (timer) timer({source}); } - }; + } - const produceEngineFullBlockOrContents = async function produceEngineFullBlockOrContents( + async function produceEngineFullBlockOrContents( slot: Slot, randaoReveal: BLSSignature, graffiti: string, @@ -408,7 +426,7 @@ export function getValidatorApi({ skipHeadChecksAndUpdate, commonBlockBody, parentBlockRoot: inParentBlockRoot, - }: Omit & + }: Omit & ( | { skipHeadChecksAndUpdate: true; @@ -417,7 +435,7 @@ export function getValidatorApi({ } | {skipHeadChecksAndUpdate?: false | undefined; commonBlockBody?: undefined; parentBlockRoot?: undefined} ) = {} - ): Promise { + ): Promise { const source = ProducedBlockSource.engine; metrics?.blockProductionRequests.inc({source}); @@ -430,7 +448,7 @@ export function getValidatorApi({ // forkChoice.updateTime() might have already been called by the onSlot clock // handler, in which case this should just return. chain.forkChoice.updateTime(slot); - parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot); + parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot); } else { parentBlockRoot = inParentBlockRoot; } @@ -448,7 +466,7 @@ export function getValidatorApi({ }); const version = config.getForkName(block.slot); if (strictFeeRecipientCheck && feeRecipient && isForkExecution(version)) { - const blockFeeRecipient = toHexString((block as bellatrix.BeaconBlock).body.executionPayload.feeRecipient); + const blockFeeRecipient = toHex((block as bellatrix.BeaconBlock).body.executionPayload.feeRecipient); if (blockFeeRecipient !== feeRecipient) { throw Error(`Invalid feeRecipient set in engine block expected=${feeRecipient} actual=${blockFeeRecipient}`); } @@ -460,7 +478,7 @@ export function getValidatorApi({ slot, executionPayloadValue, consensusBlockValue, - root: toHexString(config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block)), + root: toHex(config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block)), }); if (chain.opts.persistProducedBlocks) { void chain.persistBlock(block, "produced_engine_block"); @@ -485,329 +503,298 @@ export function getValidatorApi({ } finally { if (timer) timer({source}); } - }; + } - const produceEngineOrBuilderBlock: ServerApi["produceBlockV3"] = - async function produceEngineOrBuilderBlock( + async function produceEngineOrBuilderBlock( + slot: Slot, + randaoReveal: BLSSignature, + graffiti: string, + // TODO deneb: skip randao verification + _skipRandaoVerification?: boolean, + builderBoostFactor?: bigint, + {feeRecipient, builderSelection, strictFeeRecipientCheck}: routes.validator.ExtraProduceBlockOpts = {} + ): Promise { + notWhileSyncing(); + await waitForSlot(slot); // Must never request for a future slot > currentSlot + + // Process the queued attestations in the forkchoice for correct head estimation + // forkChoice.updateTime() might have already been called by the onSlot clock + // handler, in which case this should just return. + chain.forkChoice.updateTime(slot); + const parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot); + + const fork = config.getForkName(slot); + // set some sensible opts + // builderSelection will be deprecated and will run in mode MaxProfit if builder is enabled + // and the actual selection will be determined using builderBoostFactor passed by the validator + builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit; + builderBoostFactor = builderBoostFactor ?? BigInt(100); + if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) { + throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`); + } + + const isBuilderEnabled = + ForkSeq[fork] >= ForkSeq.bellatrix && + chain.executionBuilder !== undefined && + builderSelection !== routes.validator.BuilderSelection.ExecutionOnly; + + // At any point either the builder or execution or both flows should be active. + // + // Ideally such a scenario should be prevented on startup, but proposerSettingsFile or keymanager + // configurations could cause a validator pubkey to have builder disabled with builder selection builder only + // (TODO: independently make sure such an options update is not successful for a validator pubkey) + // + // So if builder is disabled ignore builder selection of builder only if caused by user mistake + // https://github.com/ChainSafe/lodestar/issues/6338 + const isEngineEnabled = !isBuilderEnabled || builderSelection !== routes.validator.BuilderSelection.BuilderOnly; + + if (!isEngineEnabled && !isBuilderEnabled) { + throw Error( + `Internal Error: Neither builder nor execution proposal flow activated isBuilderEnabled=${isBuilderEnabled} builderSelection=${builderSelection}` + ); + } + + const loggerContext = { slot, - randaoReveal, - graffiti, - // TODO deneb: skip randao verification - _skipRandaoVerification?: boolean, - { - feeRecipient, - builderSelection, - builderBoostFactor, - strictFeeRecipientCheck, - }: routes.validator.ExtraProduceBlockOps = {} - ) { - notWhileSyncing(); - await waitForSlot(slot); // Must never request for a future slot > currentSlot + fork, + builderSelection, + isBuilderEnabled, + isEngineEnabled, + strictFeeRecipientCheck, + // winston logger doesn't like bigint + builderBoostFactor: `${builderBoostFactor}`, + }; - // Process the queued attestations in the forkchoice for correct head estimation - // forkChoice.updateTime() might have already been called by the onSlot clock - // handler, in which case this should just return. - chain.forkChoice.updateTime(slot); - const parentBlockRoot = fromHexString(chain.getProposerHead(slot).blockRoot); - - const fork = config.getForkName(slot); - // set some sensible opts - // builderSelection will be deprecated and will run in mode MaxProfit if builder is enabled - // and the actual selection will be determined using builderBoostFactor passed by the validator - builderSelection = builderSelection ?? routes.validator.BuilderSelection.MaxProfit; - builderBoostFactor = builderBoostFactor ?? BigInt(100); - if (builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) { - throw new ApiError(400, `Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR`); - } + logger.verbose("Assembling block with produceEngineOrBuilderBlock", loggerContext); + const commonBlockBody = await chain.produceCommonBlockBody({ + slot, + parentBlockRoot, + randaoReveal, + graffiti: toGraffitiBuffer(graffiti || ""), + }); + logger.debug("Produced common block body", loggerContext); + + logger.verbose("Block production race (builder vs execution) starting", { + ...loggerContext, + cutoffMs: BLOCK_PRODUCTION_RACE_CUTOFF_MS, + timeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS, + }); + + // use abort controller to stop waiting for both block sources + const controller = new AbortController(); + + // Start calls for building execution and builder blocks + + const builderPromise = isBuilderEnabled + ? produceBuilderBlindedBlock(slot, randaoReveal, graffiti, { + feeRecipient, + // can't do fee recipient checks as builder bid doesn't return feeRecipient as of now + strictFeeRecipientCheck: false, + // skip checking and recomputing head in these individual produce calls + skipHeadChecksAndUpdate: true, + commonBlockBody, + parentBlockRoot, + }) + : Promise.reject(new Error("Builder disabled")); + + const enginePromise = isEngineEnabled + ? produceEngineFullBlockOrContents(slot, randaoReveal, graffiti, { + feeRecipient, + strictFeeRecipientCheck, + // skip checking and recomputing head in these individual produce calls + skipHeadChecksAndUpdate: true, + commonBlockBody, + parentBlockRoot, + }).then((engineBlock) => { + // Once the engine returns a block, in the event of either: + // - suspected builder censorship + // - builder boost factor set to 0 or builder selection `executionalways` + // we don't need to wait for builder block as engine block will always be selected + if ( + engineBlock.shouldOverrideBuilder || + builderBoostFactor === BigInt(0) || + builderSelection === routes.validator.BuilderSelection.ExecutionAlways + ) { + controller.abort(); + } + return engineBlock; + }) + : Promise.reject(new Error("Engine disabled")); - const isBuilderEnabled = - ForkSeq[fork] >= ForkSeq.bellatrix && - chain.executionBuilder !== undefined && - builderSelection !== routes.validator.BuilderSelection.ExecutionOnly; + const [builder, engine] = await resolveOrRacePromises([builderPromise, enginePromise], { + resolveTimeoutMs: BLOCK_PRODUCTION_RACE_CUTOFF_MS, + raceTimeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS, + signal: controller.signal, + }); - // At any point either the builder or execution or both flows should be active. - // - // Ideally such a scenario should be prevented on startup, but proposerSettingsFile or keymanager - // configurations could cause a validator pubkey to have builder disabled with builder selection builder only - // (TODO: independently make sure such an options update is not successful for a validator pubkey) - // - // So if builder is disabled ignore builder selection of builder only if caused by user mistake - // https://github.com/ChainSafe/lodestar/issues/6338 - const isEngineEnabled = !isBuilderEnabled || builderSelection !== routes.validator.BuilderSelection.BuilderOnly; + if (builder.status === "pending" && engine.status === "pending") { + throw Error("Builder and engine both failed to produce the block within timeout"); + } - if (!isEngineEnabled && !isBuilderEnabled) { - throw Error( - `Internal Error: Neither builder nor execution proposal flow activated isBuilderEnabled=${isBuilderEnabled} builderSelection=${builderSelection}` - ); - } + if (engine.status === "rejected" && isEngineEnabled) { + logger.warn( + "Engine failed to produce the block", + { + ...loggerContext, + durationMs: engine.durationMs, + }, + engine.reason + ); + } - const loggerContext = { - slot, - fork, - builderSelection, - isBuilderEnabled, - isEngineEnabled, - strictFeeRecipientCheck, - // winston logger doesn't like bigint - builderBoostFactor: `${builderBoostFactor}`, - }; + if (builder.status === "rejected" && isBuilderEnabled) { + logger.warn( + "Builder failed to produce the block", + { + ...loggerContext, + durationMs: builder.durationMs, + }, + builder.reason + ); + } - logger.verbose("Assembling block with produceEngineOrBuilderBlock", loggerContext); - const commonBlockBody = await chain.produceCommonBlockBody({ - slot, - parentBlockRoot, - randaoReveal, - graffiti: toGraffitiBuffer(graffiti || ""), - }); - logger.debug("Produced common block body", loggerContext); + if (builder.status === "rejected" && engine.status === "rejected") { + throw Error( + `${isBuilderEnabled && isEngineEnabled ? "Builder and engine both" : isBuilderEnabled ? "Builder" : "Engine"} failed to produce the block` + ); + } - logger.verbose("Block production race (builder vs execution) starting", { + // handle shouldOverrideBuilder separately + if (engine.status === "fulfilled" && engine.value.shouldOverrideBuilder) { + logger.info("Selected engine block: censorship suspected in builder blocks", { ...loggerContext, - cutoffMs: BLOCK_PRODUCTION_RACE_CUTOFF_MS, - timeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS, + durationMs: engine.durationMs, + shouldOverrideBuilder: engine.value.shouldOverrideBuilder, + ...getBlockValueLogInfo(engine.value), }); - // use abort controller to stop waiting for both block sources - const controller = new AbortController(); - - // Start calls for building execution and builder blocks - - const builderPromise = isBuilderEnabled - ? produceBuilderBlindedBlock(slot, randaoReveal, graffiti, { - feeRecipient, - // can't do fee recipient checks as builder bid doesn't return feeRecipient as of now - strictFeeRecipientCheck: false, - // skip checking and recomputing head in these individual produce calls - skipHeadChecksAndUpdate: true, - commonBlockBody, - parentBlockRoot, - }) - : Promise.reject(new Error("Builder disabled")); - - const enginePromise = isEngineEnabled - ? produceEngineFullBlockOrContents(slot, randaoReveal, graffiti, { - feeRecipient, - strictFeeRecipientCheck, - // skip checking and recomputing head in these individual produce calls - skipHeadChecksAndUpdate: true, - commonBlockBody, - parentBlockRoot, - }).then((engineBlock) => { - // Once the engine returns a block, in the event of either: - // - suspected builder censorship - // - builder boost factor set to 0 or builder selection `executionalways` - // we don't need to wait for builder block as engine block will always be selected - if ( - engineBlock.shouldOverrideBuilder || - builderBoostFactor === BigInt(0) || - builderSelection === routes.validator.BuilderSelection.ExecutionAlways - ) { - controller.abort(); - } - return engineBlock; - }) - : Promise.reject(new Error("Engine disabled")); - - const [builder, engine] = await resolveOrRacePromises([builderPromise, enginePromise], { - resolveTimeoutMs: BLOCK_PRODUCTION_RACE_CUTOFF_MS, - raceTimeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS, - signal: controller.signal, + return {...engine.value, executionPayloadBlinded: false, executionPayloadSource: ProducedBlockSource.engine}; + } + + if (builder.status === "fulfilled" && engine.status !== "fulfilled") { + logger.info("Selected builder block: no engine block produced", { + ...loggerContext, + durationMs: builder.durationMs, + ...getBlockValueLogInfo(builder.value), }); - if (builder.status === "pending" && engine.status === "pending") { - throw Error("Builder and engine both failed to produce the block within timeout"); - } + return {...builder.value, executionPayloadBlinded: true, executionPayloadSource: ProducedBlockSource.builder}; + } - if (engine.status === "rejected" && isEngineEnabled) { - logger.warn( - "Engine failed to produce the block", - { - ...loggerContext, - durationMs: engine.durationMs, - }, - engine.reason - ); - } + if (engine.status === "fulfilled" && builder.status !== "fulfilled") { + logger.info("Selected engine block: no builder block produced", { + ...loggerContext, + durationMs: engine.durationMs, + ...getBlockValueLogInfo(engine.value), + }); - if (builder.status === "rejected" && isBuilderEnabled) { - logger.warn( - "Builder failed to produce the block", - { - ...loggerContext, - durationMs: builder.durationMs, - }, - builder.reason - ); - } + return {...engine.value, executionPayloadBlinded: false, executionPayloadSource: ProducedBlockSource.engine}; + } - if (builder.status === "rejected" && engine.status === "rejected") { - throw Error( - `${isBuilderEnabled && isEngineEnabled ? "Builder and engine both" : isBuilderEnabled ? "Builder" : "Engine"} failed to produce the block` - ); - } + if (engine.status === "fulfilled" && builder.status === "fulfilled") { + const executionPayloadSource = selectBlockProductionSource({ + builderBlockValue: builder.value.executionPayloadValue + builder.value.consensusBlockValue, + engineBlockValue: engine.value.executionPayloadValue + engine.value.consensusBlockValue, + builderBoostFactor, + builderSelection, + }); - // handle shouldOverrideBuilder separately - if (engine.status === "fulfilled" && engine.value.shouldOverrideBuilder) { - logger.info("Selected engine block: censorship suspected in builder blocks", { - ...loggerContext, - durationMs: engine.durationMs, - shouldOverrideBuilder: engine.value.shouldOverrideBuilder, - ...getBlockValueLogInfo(engine.value), - }); + logger.info(`Selected ${executionPayloadSource} block`, { + ...loggerContext, + engineDurationMs: engine.durationMs, + ...getBlockValueLogInfo(engine.value, ProducedBlockSource.engine), + builderDurationMs: builder.durationMs, + ...getBlockValueLogInfo(builder.value, ProducedBlockSource.builder), + }); - return {...engine.value, executionPayloadBlinded: false, executionPayloadSource: ProducedBlockSource.engine}; + if (executionPayloadSource === ProducedBlockSource.engine) { + return { + ...engine.value, + executionPayloadBlinded: false, + executionPayloadSource, + }; + } else { + return { + ...builder.value, + executionPayloadBlinded: true, + executionPayloadSource, + }; } + } - if (builder.status === "fulfilled" && engine.status !== "fulfilled") { - logger.info("Selected builder block: no engine block produced", { - ...loggerContext, - durationMs: builder.durationMs, - ...getBlockValueLogInfo(builder.value), - }); + throw Error("Unreachable error occurred during the builder and execution block production"); + } - return {...builder.value, executionPayloadBlinded: true, executionPayloadSource: ProducedBlockSource.builder}; + return { + async produceBlock({slot, randaoReveal, graffiti}) { + const {data, ...meta} = await produceEngineFullBlockOrContents(slot, randaoReveal, graffiti); + if (isForkBlobs(meta.version)) { + throw Error(`Invalid call to produceBlock for deneb+ fork=${meta.version}`); + } else { + // TODO: need to figure out why typescript requires typecasting here + // by typing of produceFullBlockOrContents respose it should have figured this out itself + return {data: data as allForks.BeaconBlock, meta}; } + }, - if (engine.status === "fulfilled" && builder.status !== "fulfilled") { - logger.info("Selected engine block: no builder block produced", { - ...loggerContext, - durationMs: engine.durationMs, - ...getBlockValueLogInfo(engine.value), - }); - - return {...engine.value, executionPayloadBlinded: false, executionPayloadSource: ProducedBlockSource.engine}; - } + async produceBlockV2({slot, randaoReveal, graffiti, ...opts}) { + const {data, ...meta} = await produceEngineFullBlockOrContents(slot, randaoReveal, graffiti, opts); + return {data, meta}; + }, - if (engine.status === "fulfilled" && builder.status === "fulfilled") { - const executionPayloadSource = selectBlockProductionSource({ - builderBlockValue: builder.value.executionPayloadValue + builder.value.consensusBlockValue, - engineBlockValue: engine.value.executionPayloadValue + engine.value.consensusBlockValue, - builderBoostFactor, - builderSelection, - }); + async produceBlockV3({slot, randaoReveal, graffiti, skipRandaoVerification, builderBoostFactor, ...opts}) { + const {data, ...meta} = await produceEngineOrBuilderBlock( + slot, + randaoReveal, + graffiti, + skipRandaoVerification, + builderBoostFactor, + opts + ); - logger.info(`Selected ${executionPayloadSource} block`, { - ...loggerContext, - engineDurationMs: engine.durationMs, - ...getBlockValueLogInfo(engine.value, ProducedBlockSource.engine), - builderDurationMs: builder.durationMs, - ...getBlockValueLogInfo(builder.value, ProducedBlockSource.builder), - }); - - if (executionPayloadSource === ProducedBlockSource.engine) { - return { - ...engine.value, - executionPayloadBlinded: false, - executionPayloadSource, - }; + if (opts.blindedLocal === true && ForkSeq[meta.version] >= ForkSeq.bellatrix) { + if (meta.executionPayloadBlinded) { + return {data, meta}; } else { - return { - ...builder.value, - executionPayloadBlinded: true, - executionPayloadSource, - }; + if (isBlockContents(data)) { + const {block} = data; + const blindedBlock = beaconBlockToBlinded(config, block as allForks.AllForksExecution["BeaconBlock"]); + return { + data: blindedBlock, + meta: {...meta, executionPayloadBlinded: true}, + }; + } else { + const blindedBlock = beaconBlockToBlinded(config, data as allForks.AllForksExecution["BeaconBlock"]); + return { + data: blindedBlock, + meta: {...meta, executionPayloadBlinded: true}, + }; + } } + } else { + return {data, meta}; } + }, - throw Error("Unreachable error occurred during the builder and execution block production"); - }; - - const produceBlock: ServerApi["produceBlock"] = async function produceBlock( - slot, - randaoReveal, - graffiti - ) { - const producedData = await produceEngineFullBlockOrContents(slot, randaoReveal, graffiti); - if (isForkBlobs(producedData.version)) { - throw Error(`Invalid call to produceBlock for deneb+ fork=${producedData.version}`); - } else { - // TODO: need to figure out why typescript requires typecasting here - // by typing of produceFullBlockOrContents respose it should have figured this out itself - return producedData as {data: allForks.BeaconBlock}; - } - }; - - const produceEngineOrBuilderBlindedBlock: ServerApi["produceBlindedBlock"] = - async function produceEngineOrBuilderBlindedBlock(slot, randaoReveal, graffiti) { - const {data, executionPayloadValue, consensusBlockValue, version} = await produceEngineOrBuilderBlock( - slot, - randaoReveal, - graffiti - ); + async produceBlindedBlock({slot, randaoReveal, graffiti}) { + const {data, version} = await produceEngineOrBuilderBlock(slot, randaoReveal, graffiti); if (!isForkExecution(version)) { - throw Error(`Invalid fork=${version} for produceEngineOrBuilderBlindedBlock`); + throw Error(`Invalid fork=${version} for produceBlindedBlock`); } - const executionPayloadBlinded = true; if (isBlockContents(data)) { const {block} = data; const blindedBlock = beaconBlockToBlinded(config, block as allForks.AllForksExecution["BeaconBlock"]); - return {executionPayloadValue, consensusBlockValue, data: blindedBlock, executionPayloadBlinded, version}; + return {data: blindedBlock, meta: {version}}; } else if (isBlindedBeaconBlock(data)) { - return {executionPayloadValue, consensusBlockValue, data, executionPayloadBlinded, version}; + return {data, meta: {version}}; } else { const blindedBlock = beaconBlockToBlinded(config, data as allForks.AllForksExecution["BeaconBlock"]); - return {executionPayloadValue, consensusBlockValue, data: blindedBlock, executionPayloadBlinded, version}; + return {data: blindedBlock, meta: {version}}; } - }; - - const produceBlockV3: ServerApi["produceBlockV3"] = async function produceBlockV3( - slot, - randaoReveal, - graffiti, - skipRandaoVerification?: boolean, - opts: routes.validator.ExtraProduceBlockOps = {} - ) { - const produceBlockEngineOrBuilderRes = await produceEngineOrBuilderBlock( - slot, - randaoReveal, - graffiti, - skipRandaoVerification, - opts - ); - - if (opts.blindedLocal === true && ForkSeq[produceBlockEngineOrBuilderRes.version] >= ForkSeq.bellatrix) { - if (produceBlockEngineOrBuilderRes.executionPayloadBlinded) { - return produceBlockEngineOrBuilderRes; - } else { - if (isBlockContents(produceBlockEngineOrBuilderRes.data)) { - const {block} = produceBlockEngineOrBuilderRes.data; - const blindedBlock = beaconBlockToBlinded(config, block as allForks.AllForksExecution["BeaconBlock"]); - return { - ...produceBlockEngineOrBuilderRes, - data: blindedBlock, - executionPayloadBlinded: true, - } as routes.validator.ProduceBlindedBlockRes & { - executionPayloadBlinded: true; - executionPayloadSource: ProducedBlockSource; - }; - } else { - const blindedBlock = beaconBlockToBlinded( - config, - produceBlockEngineOrBuilderRes.data as allForks.AllForksExecution["BeaconBlock"] - ); - return { - ...produceBlockEngineOrBuilderRes, - data: blindedBlock, - executionPayloadBlinded: true, - } as routes.validator.ProduceBlindedBlockRes & { - executionPayloadBlinded: true; - executionPayloadSource: ProducedBlockSource; - }; - } - } - } else { - return produceBlockEngineOrBuilderRes; - } - }; - - return { - produceBlock, - produceBlockV2: produceEngineFullBlockOrContents, - produceBlockV3, - produceBlindedBlock: produceEngineOrBuilderBlindedBlock, + }, - async produceAttestationData(committeeIndex, slot) { + async produceAttestationData({committeeIndex, slot}) { notWhileSyncing(); await waitForSlot(slot); // Must never request for a future slot > currentSlot @@ -818,7 +805,7 @@ export function getValidatorApi({ const headSlot = headState.slot; const attEpoch = computeEpochAtSlot(slot); const headBlockRootHex = chain.forkChoice.getHead().blockRoot; - const headBlockRoot = fromHexString(headBlockRootHex); + const headBlockRoot = fromHex(headBlockRootHex); const beaconBlockRoot = slot >= headSlot @@ -865,12 +852,12 @@ export function getValidatorApi({ * @param subcommitteeIndex The subcommittee index for which to produce the contribution. * @param beaconBlockRoot The block root for which to produce the contribution. */ - async produceSyncCommitteeContribution(slot, subcommitteeIndex, beaconBlockRoot) { + async produceSyncCommitteeContribution({slot, subcommitteeIndex, beaconBlockRoot}) { // when a validator is configured with multiple beacon node urls, this beaconBlockRoot may come from another beacon node // and it hasn't been in our forkchoice since we haven't seen / processing that block // see https://github.com/ChainSafe/lodestar/issues/5063 if (!chain.forkChoice.hasBlock(beaconBlockRoot)) { - const rootHex = toHexString(beaconBlockRoot); + const rootHex = toHex(beaconBlockRoot); network.searchUnknownSlotRoot({slot, root: rootHex}); // if result of this call is false, i.e. block hasn't seen after 1 slot then the below notOnOptimisticBlockRoot call will throw error await chain.waitForBlock(slot, rootHex); @@ -889,7 +876,7 @@ export function getValidatorApi({ return {data: contribution}; }, - async getProposerDuties(epoch) { + async getProposerDuties({epoch}) { notWhileSyncing(); // Early check that epoch is within [current_epoch, current_epoch + 1], or allow for pre-genesis @@ -953,15 +940,17 @@ export function getValidatorApi({ return { data: duties, - dependentRoot: toHex(dependentRoot), - executionOptimistic: isOptimisticBlock(head), + meta: { + dependentRoot: toHex(dependentRoot), + executionOptimistic: isOptimisticBlock(head), + }, }; }, - async getAttesterDuties(epoch, validatorIndices) { + async getAttesterDuties({epoch, indices}) { notWhileSyncing(); - if (validatorIndices.length === 0) { + if (indices.length === 0) { throw new ApiError(400, "No validator to get attester duties"); } @@ -985,11 +974,11 @@ export function getValidatorApi({ // will equal `currentEpoch + 1` // Check that all validatorIndex belong to the state before calling getCommitteeAssignments() - const pubkeys = getPubkeysForIndices(state.validators, validatorIndices); - const committeeAssignments = state.epochCtx.getCommitteeAssignments(epoch, validatorIndices); + const pubkeys = getPubkeysForIndices(state.validators, indices); + const committeeAssignments = state.epochCtx.getCommitteeAssignments(epoch, indices); const duties: routes.validator.AttesterDuty[] = []; - for (let i = 0, len = validatorIndices.length; i < len; i++) { - const validatorIndex = validatorIndices[i]; + for (let i = 0, len = indices.length; i < len; i++) { + const validatorIndex = indices[i]; const duty = committeeAssignments.get(validatorIndex) as routes.validator.AttesterDuty | undefined; if (duty) { // Mutate existing object instead of re-creating another new object with spread operator @@ -1003,8 +992,10 @@ export function getValidatorApi({ return { data: duties, - dependentRoot: toHex(dependentRoot), - executionOptimistic: isOptimisticBlock(head), + meta: { + dependentRoot: toHex(dependentRoot), + executionOptimistic: isOptimisticBlock(head), + }, }; }, @@ -1021,10 +1012,10 @@ export function getValidatorApi({ * * @param validatorIndices an array of the validator indices for which to obtain the duties. */ - async getSyncCommitteeDuties(epoch, validatorIndices) { + async getSyncCommitteeDuties({epoch, indices}) { notWhileSyncing(); - if (validatorIndices.length === 0) { + if (indices.length === 0) { throw new ApiError(400, "No validator to get attester duties"); } @@ -1038,14 +1029,14 @@ export function getValidatorApi({ const state = chain.getHeadState(); // Check that all validatorIndex belong to the state before calling getCommitteeAssignments() - const pubkeys = getPubkeysForIndices(state.validators, validatorIndices); + const pubkeys = getPubkeysForIndices(state.validators, indices); // Ensures `epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD <= current_epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + 1` const syncCommitteeCache = state.epochCtx.getIndexedSyncCommitteeAtEpoch(epoch); const syncCommitteeValidatorIndexMap = syncCommitteeCache.validatorIndexMap; const duties: routes.validator.SyncDuty[] = []; - for (let i = 0, len = validatorIndices.length; i < len; i++) { - const validatorIndex = validatorIndices[i]; + for (let i = 0, len = indices.length; i < len; i++) { + const validatorIndex = indices[i]; const validatorSyncCommitteeIndices = syncCommitteeValidatorIndexMap.get(validatorIndex); if (validatorSyncCommitteeIndices) { duties.push({ @@ -1058,16 +1049,16 @@ export function getValidatorApi({ return { data: duties, - executionOptimistic: isOptimisticBlock(head), + meta: {executionOptimistic: isOptimisticBlock(head)}, }; }, - async getAggregatedAttestation(attestationDataRoot, slot) { + async getAggregatedAttestation({attestationDataRoot, slot}) { notWhileSyncing(); await waitForSlot(slot); // Must never request for a future slot > currentSlot - const dataRootHex = toHexString(attestationDataRoot); + const dataRootHex = toHex(attestationDataRoot); const aggregate = chain.attestationPool.getAggregate(slot, dataRootHex); if (!aggregate) { @@ -1081,7 +1072,7 @@ export function getValidatorApi({ }; }, - async publishAggregateAndProofs(signedAggregateAndProofs) { + async publishAggregateAndProofs({signedAggregateAndProofs}) { notWhileSyncing(); const seenTimestampSec = Date.now() / 1000; @@ -1148,7 +1139,7 @@ export function getValidatorApi({ * * https://github.com/ethereum/beacon-APIs/pull/137 */ - async publishContributionAndProofs(contributionAndProofs) { + async publishContributionAndProofs({contributionAndProofs}) { notWhileSyncing(); const errors: Error[] = []; @@ -1197,7 +1188,7 @@ export function getValidatorApi({ } }, - async prepareBeaconCommitteeSubnet(subscriptions) { + async prepareBeaconCommitteeSubnet({subscriptions}) { notWhileSyncing(); await network.prepareBeaconCommitteeSubnets( @@ -1230,7 +1221,7 @@ export function getValidatorApi({ * * https://github.com/ethereum/beacon-APIs/pull/136 */ - async prepareSyncCommitteeSubnets(subscriptions) { + async prepareSyncCommitteeSubnets({subscriptions}) { notWhileSyncing(); // A `validatorIndex` can be in multiple subnets, so compute the CommitteeSubscription with double for loop @@ -1257,7 +1248,7 @@ export function getValidatorApi({ } }, - async prepareBeaconProposer(proposers) { + async prepareBeaconProposer({proposers}) { await chain.updateBeaconProposerData(chain.clock.currentEpoch, proposers); }, @@ -1269,8 +1260,8 @@ export function getValidatorApi({ throw new OnlySupportedByDVT(); }, - async getLiveness(epoch, validatorIndices) { - if (validatorIndices.length === 0) { + async getLiveness({epoch, indices}) { + if (indices.length === 0) { return { data: [], }; @@ -1284,14 +1275,14 @@ export function getValidatorApi({ } return { - data: validatorIndices.map((index) => ({ + data: indices.map((index) => ({ index, isLive: chain.validatorSeenAtEpoch(index, epoch), })), }; }, - async registerValidator(registrations) { + async registerValidator({registrations}) { if (!chain.executionBuilder) { throw Error("Execution builder not enabled"); } diff --git a/packages/beacon-node/src/api/rest/base.ts b/packages/beacon-node/src/api/rest/base.ts index 86eab0ba46d4..20439290e939 100644 --- a/packages/beacon-node/src/api/rest/base.ts +++ b/packages/beacon-node/src/api/rest/base.ts @@ -2,6 +2,7 @@ import {parse as parseQueryString} from "qs"; import {FastifyInstance, FastifyRequest, fastify, errorCodes} from "fastify"; import {fastifyCors} from "@fastify/cors"; import bearerAuthPlugin from "@fastify/bearer-auth"; +import {addSszContentTypeParser} from "@lodestar/api/server"; import {ErrorAborted, Gauge, Histogram, Logger} from "@lodestar/utils"; import {isLocalhostIP} from "../../util/ip.js"; import {ApiError, NodeIsSyncing} from "../impl/errors.js"; @@ -64,6 +65,8 @@ export class RestApiServer { http: {maxHeaderSize: opts.headerLimit}, }); + addSszContentTypeParser(server); + this.activeSockets = new HttpActiveSocketsTracker(server.server, metrics); // To parse our ApiError -> statusCode diff --git a/packages/beacon-node/src/api/rest/index.ts b/packages/beacon-node/src/api/rest/index.ts index e46d1ab6d9bd..e27ed6bd9139 100644 --- a/packages/beacon-node/src/api/rest/index.ts +++ b/packages/beacon-node/src/api/rest/index.ts @@ -1,4 +1,5 @@ -import {Api, ServerApi} from "@lodestar/api"; +import {Endpoints} from "@lodestar/api"; +import {BeaconApiMethods} from "@lodestar/api/beacon/server"; import {registerRoutes} from "@lodestar/api/beacon/server"; import {ErrorAborted, Logger} from "@lodestar/utils"; import {ChainForkConfig} from "@lodestar/config"; @@ -10,7 +11,7 @@ export {allNamespaces} from "@lodestar/api"; export type BeaconRestApiServerOpts = Omit & { enabled: boolean; - api: (keyof Api)[]; + api: (keyof Endpoints)[]; }; export const beaconRestApiServerOpts: BeaconRestApiServerOpts = { @@ -26,7 +27,7 @@ export const beaconRestApiServerOpts: BeaconRestApiServerOpts = { export type BeaconRestApiServerModules = RestApiServerModules & { config: ChainForkConfig; logger: Logger; - api: {[K in keyof Api]: ServerApi}; + api: BeaconApiMethods; metrics: RestApiServerMetrics | null; }; diff --git a/packages/beacon-node/src/chain/beaconProposerCache.ts b/packages/beacon-node/src/chain/beaconProposerCache.ts index c6149b0232c9..f8638aa64bf7 100644 --- a/packages/beacon-node/src/chain/beaconProposerCache.ts +++ b/packages/beacon-node/src/chain/beaconProposerCache.ts @@ -8,7 +8,7 @@ const PROPOSER_PRESERVE_EPOCHS = 2; export type ProposerPreparationData = routes.validator.ProposerPreparationData; export class BeaconProposerCache { - private readonly feeRecipientByValidatorIndex: MapDef; + private readonly feeRecipientByValidatorIndex: MapDef; constructor( opts: {suggestedFeeRecipient: string}, private readonly metrics?: Metrics | null @@ -33,11 +33,11 @@ export class BeaconProposerCache { } } - getOrDefault(proposerIndex: number | string): string { - return this.feeRecipientByValidatorIndex.getOrDefault(`${proposerIndex}`).feeRecipient; + getOrDefault(proposerIndex: number): string { + return this.feeRecipientByValidatorIndex.getOrDefault(proposerIndex).feeRecipient; } - get(proposerIndex: number | string): string | undefined { - return this.feeRecipientByValidatorIndex.get(`${proposerIndex}`)?.feeRecipient; + get(proposerIndex: number): string | undefined { + return this.feeRecipientByValidatorIndex.get(proposerIndex)?.feeRecipient; } } diff --git a/packages/beacon-node/src/execution/builder/http.ts b/packages/beacon-node/src/execution/builder/http.ts index 9e26faf90b63..7637023a9c08 100644 --- a/packages/beacon-node/src/execution/builder/http.ts +++ b/packages/beacon-node/src/execution/builder/http.ts @@ -2,10 +2,9 @@ import {allForks, bellatrix, Slot, Root, BLSPubkey, deneb, Wei} from "@lodestar/ import {parseExecutionPayloadAndBlobsBundle, reconstructFullBlockOrContents} from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; import {Logger} from "@lodestar/logger"; -import {getClient, Api as BuilderApi} from "@lodestar/api/builder"; +import {getClient, ApiClient as BuilderApi} from "@lodestar/api/builder"; import {SLOTS_PER_EPOCH, ForkExecution} from "@lodestar/params"; import {toSafePrintableUrl} from "@lodestar/utils"; -import {ApiError} from "@lodestar/api"; import {Metrics} from "../../metrics/metrics.js"; import {IExecutionBuilder} from "./interface.js"; @@ -48,8 +47,10 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { this.api = getClient( { baseUrl, - timeoutMs: opts.timeout, - extraHeaders: opts.userAgent ? {"User-Agent": opts.userAgent} : undefined, + globalInit: { + timeoutMs: opts.timeout, + headers: opts.userAgent ? {"User-Agent": opts.userAgent} : undefined, + }, }, {config, metrics: metrics?.builderHttpClient} ); @@ -82,7 +83,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { async checkStatus(): Promise { try { - await this.api.status(); + (await this.api.status()).assertOk(); } catch (e) { // Disable if the status was enabled this.status = false; @@ -91,35 +92,34 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { } async registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise { - ApiError.assert( - await this.api.registerValidator(registrations), - "Failed to forward validator registrations to connected builder" - ); + (await this.api.registerValidator({registrations})).assertOk(); } async getHeader( - fork: ForkExecution, + _fork: ForkExecution, slot: Slot, parentHash: Root, - proposerPubKey: BLSPubkey + proposerPubkey: BLSPubkey ): Promise<{ header: allForks.ExecutionPayloadHeader; executionPayloadValue: Wei; blobKzgCommitments?: deneb.BlobKzgCommitments; }> { - const res = await this.api.getHeader(slot, parentHash, proposerPubKey); - ApiError.assert(res, "execution.builder.getheader"); - const {header, value: executionPayloadValue} = res.response.data.message; - const {blobKzgCommitments} = res.response.data.message as deneb.BuilderBid; + const signedBuilderBid = (await this.api.getHeader({slot, parentHash, proposerPubkey})).value(); + + if (!signedBuilderBid) { + throw Error("No bid received"); + } + + const {header, value: executionPayloadValue} = signedBuilderBid.message; + const {blobKzgCommitments} = signedBuilderBid.message as deneb.BuilderBid; return {header, executionPayloadValue, blobKzgCommitments}; } async submitBlindedBlock( signedBlindedBlock: allForks.SignedBlindedBeaconBlock ): Promise { - const res = await this.api.submitBlindedBlock(signedBlindedBlock, {retries: 2}); - ApiError.assert(res, "execution.builder.submitBlindedBlock"); - const {data} = res.response; + const data = (await this.api.submitBlindedBlock({signedBlindedBlock}, {retries: 2})).value(); const {executionPayload, blobsBundle} = parseExecutionPayloadAndBlobsBundle(data); diff --git a/packages/beacon-node/src/node/nodejs.ts b/packages/beacon-node/src/node/nodejs.ts index 1e9c7794ac68..a1147b60bea2 100644 --- a/packages/beacon-node/src/node/nodejs.ts +++ b/packages/beacon-node/src/node/nodejs.ts @@ -6,7 +6,7 @@ import {BeaconConfig} from "@lodestar/config"; import {phase0} from "@lodestar/types"; import {sleep} from "@lodestar/utils"; import type {LoggerNode} from "@lodestar/logger/node"; -import {Api, ServerApi} from "@lodestar/api"; +import {BeaconApiMethods} from "@lodestar/api/beacon/server"; import {BeaconStateAllForks} from "@lodestar/state-transition"; import {ProcessShutdownCallback} from "@lodestar/validator"; @@ -33,7 +33,7 @@ export type BeaconNodeModules = { metrics: Metrics | null; network: Network; chain: IBeaconChain; - api: {[K in keyof Api]: ServerApi}; + api: BeaconApiMethods; sync: IBeaconSync; backfillSync: BackfillSync | null; metricsServer: HttpMetricsServer | null; @@ -95,7 +95,7 @@ export class BeaconNode { monitoring: MonitoringService | null; network: Network; chain: IBeaconChain; - api: {[K in keyof Api]: ServerApi}; + api: BeaconApiMethods; restApi?: BeaconRestApiServer; sync: IBeaconSync; backfillSync: BackfillSync | null; 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 89d98902676b..d85fdb80720f 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,8 +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 {Api, getClient} from "@lodestar/api/beacon"; -import {ApiError} from "@lodestar/api"; +import {ApiClient, getClient} from "@lodestar/api/beacon"; import {sleep} from "@lodestar/utils"; import {LogLevel, testLogger} from "../../../../../utils/logger.js"; import {getDevBeaconNode} from "../../../../../utils/node/beacon.js"; @@ -17,7 +16,7 @@ describe("beacon node api", function () { const validatorCount = 8; let bn: BeaconNode; - let client: Api; + let client: ApiClient; beforeAll(async () => { bn = await getDevBeaconNode({ @@ -46,9 +45,8 @@ describe("beacon node api", function () { describe("getSyncingStatus", () => { it("should return valid syncing status", async () => { const res = await client.node.getSyncingStatus(); - ApiError.assert(res); - expect(res.response.data).toEqual({ + expect(res.value()).toEqual({ headSlot: "0", syncDistance: "0", isSyncing: false, @@ -59,9 +57,8 @@ describe("beacon node api", function () { it("should return 'el_offline' as 'true' for default dev node", async () => { const res = await client.node.getSyncingStatus(); - ApiError.assert(res); - expect(res.response.data.elOffline).toEqual(false); + expect(res.value().elOffline).toEqual(false); }); it("should return 'el_offline' as 'true' when EL not available", async () => { @@ -103,9 +100,8 @@ describe("beacon node api", function () { await sleep(chainConfigDef.SECONDS_PER_SLOT * 2 * 1000); const res = await clientElOffline.node.getSyncingStatus(); - ApiError.assert(res); - expect(res.response.data.elOffline).toEqual(true); + expect(res.value().elOffline).toEqual(true); await Promise.all(validators.map((v) => v.close())); await bnElOffline.close(); @@ -116,7 +112,7 @@ describe("beacon node api", function () { const portSyncing = 9598; let bnSyncing: BeaconNode; - let clientSyncing: Api; + let clientSyncing: ApiClient; beforeAll(async () => { bnSyncing = await getDevBeaconNode({ diff --git a/packages/beacon-node/test/e2e/api/impl/beacon/state/endpoint.test.ts b/packages/beacon-node/test/e2e/api/impl/beacon/state/endpoint.test.ts index 9ab3da7e53f9..01423d72341f 100644 --- a/packages/beacon-node/test/e2e/api/impl/beacon/state/endpoint.test.ts +++ b/packages/beacon-node/test/e2e/api/impl/beacon/state/endpoint.test.ts @@ -2,7 +2,7 @@ import {describe, beforeAll, afterAll, it, expect} from "vitest"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {createBeaconConfig} from "@lodestar/config"; import {chainConfig as chainConfigDef} from "@lodestar/config/default"; -import {Api, ApiError, getClient} from "@lodestar/api"; +import {ApiClient, getClient} from "@lodestar/api"; import {computeCommitteeCount} from "@lodestar/state-transition"; import {LogLevel, testLogger} from "../../../../../utils/logger.js"; import {getDevBeaconNode} from "../../../../../utils/node/beacon.js"; @@ -17,7 +17,7 @@ describe("beacon state api", function () { const validatorsPerCommittee = validatorCount / committeeCount; let bn: BeaconNode; - let client: Api["beacon"]; + let client: ApiClient["beacon"]; beforeAll(async () => { bn = await getDevBeaconNode({ @@ -45,9 +45,9 @@ describe("beacon state api", function () { describe("getEpochCommittees", () => { it("should return all committees for the given state", async () => { - const res = await client.getEpochCommittees("head"); - ApiError.assert(res); - const {data: epochCommittees, executionOptimistic, finalized} = res.response; + const res = await client.getEpochCommittees({stateId: "head"}); + const epochCommittees = res.value(); + const {executionOptimistic, finalized} = res.meta(); expect(epochCommittees).toHaveLength(committeeCount); expect(executionOptimistic).toBe(false); @@ -77,9 +77,7 @@ describe("beacon state api", function () { it("should restrict returned committees to those matching the supplied index", async () => { const index = committeesPerSlot / 2; - const res = await client.getEpochCommittees("head", {index}); - ApiError.assert(res); - const epochCommittees = res.response.data; + const epochCommittees = (await client.getEpochCommittees({stateId: "head", index})).value(); expect(epochCommittees).toHaveLength(SLOTS_PER_EPOCH); for (const committee of epochCommittees) { expect(committee.index).toBeWithMessage(index, "Committee index does not match supplied index"); @@ -88,9 +86,7 @@ describe("beacon state api", function () { it("should restrict returned committees to those matching the supplied slot", async () => { const slot = SLOTS_PER_EPOCH / 2; - const res = await client.getEpochCommittees("head", {slot}); - ApiError.assert(res); - const epochCommittees = res.response.data; + const epochCommittees = (await client.getEpochCommittees({stateId: "head", slot})).value(); expect(epochCommittees).toHaveLength(committeesPerSlot); for (const committee of epochCommittees) { expect(committee.slot).toBeWithMessage(slot, "Committee slot does not match supplied slot"); 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 8c83667d5ca5..0f13575a5ec4 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 @@ -2,7 +2,7 @@ import {describe, it, beforeEach, afterEach, expect} from "vitest"; import bls from "@chainsafe/bls"; import {createBeaconConfig, ChainConfig} from "@lodestar/config"; import {chainConfig as chainConfigDef} from "@lodestar/config/default"; -import {ApiError, getClient, routes} from "@lodestar/api"; +import {getClient, routes} from "@lodestar/api"; import {sleep} from "@lodestar/utils"; import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; import {Validator} from "@lodestar/validator"; @@ -81,59 +81,55 @@ describe("lightclient api", function () { await sleep(2 * SECONDS_PER_SLOT * 1000); }; - it("getUpdates()", async function () { + it("getLightClientUpdatesByRange()", async function () { const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).lightclient; await waitForBestUpdate(); - const res = await client.getUpdates(0, 1); - ApiError.assert(res); - const updates = res.response; + const res = await client.getLightClientUpdatesByRange({startPeriod: 0, count: 1}); + const updates = res.value(); expect(updates.length).toBe(1); // best update could be any slots // version is set - expect(updates[0].version).toBe(ForkName.altair); + expect(res.meta().versions[0]).toBe(ForkName.altair); }); - it("getOptimisticUpdate()", async function () { + it("getLightClientOptimisticUpdate()", async function () { await waitForBestUpdate(); const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).lightclient; - const res = await client.getOptimisticUpdate(); - ApiError.assert(res); - const update = res.response; + const res = await client.getLightClientOptimisticUpdate(); + const update = res.value(); const slot = bn.chain.clock.currentSlot; // at slot 2 we got attestedHeader for slot 1 - expect(update.data.attestedHeader.beacon.slot).toBe(slot - 1); + expect(update.attestedHeader.beacon.slot).toBe(slot - 1); // version is set - expect(update.version).toBe(ForkName.altair); + expect(res.meta().version).toBe(ForkName.altair); }); - it.skip("getFinalityUpdate()", async function () { + it.skip("getLightClientFinalityUpdate()", async function () { // TODO: not sure how this causes subsequent tests failed await waitForEvent(bn.chain.emitter, routes.events.EventType.finalizedCheckpoint, 240000); await sleep(SECONDS_PER_SLOT * 1000); const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).lightclient; - const res = await client.getFinalityUpdate(); - ApiError.assert(res); - expect(res.response).toBeDefined(); + const finalityUpdate = (await client.getLightClientFinalityUpdate()).value(); + expect(finalityUpdate).toBeDefined(); }); - it("getCommitteeRoot() for the 1st period", async function () { + it("getLightClientCommitteeRoot() for the 1st period", async function () { await waitForBestUpdate(); const lightclient = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).lightclient; - const committeeRes = await lightclient.getCommitteeRoot(0, 1); - ApiError.assert(committeeRes); + const committeeRes = await lightclient.getLightClientCommitteeRoot({startPeriod: 0, count: 1}); + committeeRes.assertOk(); const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).beacon; - const validatorResponse = await client.getStateValidators("head"); - ApiError.assert(validatorResponse); - const pubkeys = validatorResponse.response.data.map((v) => v.validator.pubkey); + const validators = (await client.getStateValidators({stateId: "head"})).value(); + const pubkeys = validators.map((v) => v.validator.pubkey); expect(pubkeys.length).toBe(validatorCount); // only 2 validators spreading to 512 committee slots const committeePubkeys = Array.from({length: SYNC_COMMITTEE_SIZE}, (_, i) => i % 2 === 0 ? pubkeys[0] : pubkeys[1] ); const aggregatePubkey = bls.aggregatePublicKeys(committeePubkeys); - // single committe hash since we requested for the first period - expect(committeeRes.response.data).toEqual([ + // single committee hash since we requested for the first period + expect(committeeRes.value()).toEqual([ ssz.altair.SyncCommittee.hashTreeRoot({ pubkeys: committeePubkeys, aggregatePubkey, diff --git a/packages/beacon-node/test/e2e/api/lodestar/lodestar.test.ts b/packages/beacon-node/test/e2e/api/lodestar/lodestar.test.ts index e9d02beb6835..5cccb2aa0772 100644 --- a/packages/beacon-node/test/e2e/api/lodestar/lodestar.test.ts +++ b/packages/beacon-node/test/e2e/api/lodestar/lodestar.test.ts @@ -3,7 +3,7 @@ import {createBeaconConfig, ChainConfig} from "@lodestar/config"; import {chainConfig as chainConfigDef} from "@lodestar/config/default"; import {phase0} from "@lodestar/types"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {getClient, HttpStatusCode} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {LogLevel, testLogger, TestLoggerOpts} from "../../../utils/logger.js"; import {getDevBeaconNode} from "../../../utils/node/beacon.js"; import {waitForEvent} from "../../../utils/events/resolver.js"; @@ -61,19 +61,15 @@ describe("api / impl / validator", function () { const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}); - await expect(client.validator.getLiveness(0, [1, 2, 3, 4, 5])).resolves.toEqual({ - response: { - data: [ - {index: 1, isLive: true}, - {index: 2, isLive: true}, - {index: 3, isLive: true}, - {index: 4, isLive: true}, - {index: 5, isLive: false}, - ], - }, - ok: true, - status: HttpStatusCode.OK, - }); + const res = await client.validator.getLiveness({epoch: 0, indices: [1, 2, 3, 4, 5]}); + + expect(res.value()).toEqual([ + {index: 1, isLive: true}, + {index: 2, isLive: true}, + {index: 3, isLive: true}, + {index: 4, isLive: true}, + {index: 5, isLive: false}, + ]); }); it("Should return only for previous, current and next epoch", async function () { @@ -107,23 +103,23 @@ describe("api / impl / validator", function () { const previousEpoch = currentEpoch - 1; // current epoch is fine - await expect(client.validator.getLiveness(currentEpoch, [1])).resolves.toBeDefined(); + (await client.validator.getLiveness({epoch: currentEpoch, indices: [1]})).assertOk(); // next epoch is fine - await expect(client.validator.getLiveness(nextEpoch, [1])).resolves.toBeDefined(); + (await client.validator.getLiveness({epoch: nextEpoch, indices: [1]})).assertOk(); // previous epoch is fine - await expect(client.validator.getLiveness(previousEpoch, [1])).resolves.toBeDefined(); + (await client.validator.getLiveness({epoch: previousEpoch, indices: [1]})).assertOk(); // more than next epoch is not fine - const res1 = await client.validator.getLiveness(currentEpoch + 2, [1]); + const res1 = await client.validator.getLiveness({epoch: currentEpoch + 2, indices: [1]}); expect(res1.ok).toBe(false); - expect(res1.error?.message).toEqual( + expect(res1.error()?.message).toEqual( expect.stringContaining( `Request epoch ${currentEpoch + 2} is more than one epoch before or after the current epoch ${currentEpoch}` ) ); // more than previous epoch is not fine - const res2 = await client.validator.getLiveness(currentEpoch - 2, [1]); + const res2 = await client.validator.getLiveness({epoch: currentEpoch - 2, indices: [1]}); expect(res2.ok).toBe(false); - expect(res2.error?.message).toEqual( + expect(res2.error()?.message).toEqual( expect.stringContaining( `Request epoch ${currentEpoch - 2} is more than one epoch before or after the current epoch ${currentEpoch}` ) diff --git a/packages/beacon-node/test/e2e/chain/lightclient.test.ts b/packages/beacon-node/test/e2e/chain/lightclient.test.ts index dc440d5982ec..c501a2618049 100644 --- a/packages/beacon-node/test/e2e/chain/lightclient.test.ts +++ b/packages/beacon-node/test/e2e/chain/lightclient.test.ts @@ -1,6 +1,6 @@ import {describe, it, expect, afterEach, vi} from "vitest"; import {JsonPath, toHexString, fromHexString} from "@chainsafe/ssz"; -import {computeDescriptor, TreeOffsetProof} from "@chainsafe/persistent-merkle-tree"; +import {CompactMultiProof, computeDescriptor} from "@chainsafe/persistent-merkle-tree"; import {ChainConfig} from "@lodestar/config"; import {ssz, altair} from "@lodestar/types"; import {TimestampFormatCode} from "@lodestar/logger"; @@ -8,7 +8,7 @@ import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/param import {Lightclient} from "@lodestar/light-client"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {LightClientRestTransport} from "@lodestar/light-client/transport"; -import {Api, ApiError, getClient, routes} from "@lodestar/api"; +import {ApiClient, getClient, routes} from "@lodestar/api"; import {testLogger, LogLevel, TestLoggerOpts} from "../../utils/logger.js"; import {getDevBeaconNode} from "../../utils/node/beacon.js"; import {getAndInitDevValidators} from "../../utils/node/validator.js"; @@ -183,17 +183,13 @@ describe("chain / lightclient", function () { // TODO: Re-incorporate for REST-only light-client async function getHeadStateProof( lightclient: Lightclient, - api: Api, + api: ApiClient, paths: JsonPath[] -): Promise<{proof: TreeOffsetProof; header: altair.LightClientHeader}> { +): Promise<{proof: CompactMultiProof; header: altair.LightClientHeader}> { const header = lightclient.getHead(); const stateId = toHexString(header.beacon.stateRoot); const gindices = paths.map((path) => ssz.bellatrix.BeaconState.getPathInfo(path).gindex); const descriptor = computeDescriptor(gindices); - const res = await api.proof.getStateProof(stateId, descriptor); - ApiError.assert(res); - return { - proof: res.response.data as TreeOffsetProof, - header, - }; + const proof = (await api.proof.getStateProof({stateId, descriptor})).value(); + return {proof, header}; } diff --git a/packages/beacon-node/test/scripts/blsPubkeyBytesFrequency.ts b/packages/beacon-node/test/scripts/blsPubkeyBytesFrequency.ts index c8cd742a6d3f..1889cacf1496 100644 --- a/packages/beacon-node/test/scripts/blsPubkeyBytesFrequency.ts +++ b/packages/beacon-node/test/scripts/blsPubkeyBytesFrequency.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import {digest} from "@chainsafe/as-sha256"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {newZeroedArray} from "@lodestar/state-transition"; @@ -112,12 +112,9 @@ async function writePubkeys(): Promise { const client = getClient({baseUrl}, {config}); - const res = await client.debug.getStateV2("finalized"); - ApiError.assert(res); + const state = (await client.debug.getStateV2({stateId: "finalized"})).value(); - const pubkeys = Array.from(res.response.data.validators).map((validator) => - Buffer.from(validator.pubkey).toString("hex") - ); + const pubkeys = Array.from(state.validators).map((validator) => Buffer.from(validator.pubkey).toString("hex")); fs.writeFileSync("mainnet_pubkeys.csv", pubkeys.join("\n")); } diff --git a/packages/beacon-node/test/sim/mergemock.test.ts b/packages/beacon-node/test/sim/mergemock.test.ts index 4cce95967c55..3705b845d805 100644 --- a/packages/beacon-node/test/sim/mergemock.test.ts +++ b/packages/beacon-node/test/sim/mergemock.test.ts @@ -212,7 +212,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { let builderBlocks = 0; await new Promise((resolve, _reject) => { bn.chain.emitter.on(routes.events.EventType.block, async (blockData) => { - const {data: fullOrBlindedBlock} = (await bn.api.beacon.getBlockV2(blockData.block)) as { + const {data: fullOrBlindedBlock} = (await bn.api.beacon.getBlockV2({blockId: blockData.block})) as { data: allForks.SignedBeaconBlock; }; if (fullOrBlindedBlock !== undefined) { diff --git a/packages/beacon-node/test/unit/api/impl/beacon/beacon.test.ts b/packages/beacon-node/test/unit/api/impl/beacon/beacon.test.ts index 28df366a0cde..a8eaffa42005 100644 --- a/packages/beacon-node/test/unit/api/impl/beacon/beacon.test.ts +++ b/packages/beacon-node/test/unit/api/impl/beacon/beacon.test.ts @@ -1,4 +1,5 @@ import {describe, it, expect, beforeAll} from "vitest"; +import {phase0} from "@lodestar/types"; import {ApiTestModules, getApiTestModules} from "../../../../utils/api.js"; import {getBeaconApi} from "../../../../../src/api/impl/beacon/index.js"; import {Mutable} from "../../../../utils/types.js"; @@ -17,7 +18,7 @@ describe("beacon api implementation", function () { (modules.chain as Mutable).genesisTime = 0; (modules.chain as Mutable).genesisValidatorsRoot = Buffer.alloc(32); - const {data: genesis} = await api.getGenesis(); + const {data: genesis} = (await api.getGenesis()) as {data: phase0.Genesis}; if (genesis === null || genesis === undefined) throw Error("Genesis is nullish"); expect(genesis.genesisForkVersion).toBeDefined(); expect(genesis.genesisTime).toBeDefined(); diff --git a/packages/beacon-node/test/unit/api/impl/beacon/blocks/getBlockHeaders.test.ts b/packages/beacon-node/test/unit/api/impl/beacon/blocks/getBlockHeaders.test.ts index 38a9e6677639..9b3c960ff2ec 100644 --- a/packages/beacon-node/test/unit/api/impl/beacon/blocks/getBlockHeaders.test.ts +++ b/packages/beacon-node/test/unit/api/impl/beacon/blocks/getBlockHeaders.test.ts @@ -1,6 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; import {describe, it, expect, beforeEach, vi, afterEach} from "vitest"; import {when} from "vitest-when"; +import {routes} from "@lodestar/api"; import {ssz} from "@lodestar/types"; import {ApiTestModules, getApiTestModules} from "../../../../../utils/api.js"; import {generateProtoBlock, generateSignedBlockAtSlot} from "../../../../../utils/typeGenerator.js"; @@ -44,7 +45,7 @@ describe("api - beacon - getBlockHeaders", function () { modules.db.block.get.mockResolvedValue(blockFromDb3); modules.db.blockArchive.get.mockResolvedValue(null); - const {data: blockHeaders} = await api.getBlockHeaders({}); + const {data: blockHeaders} = (await api.getBlockHeaders({})) as {data: routes.beacon.BlockHeaderResponse[]}; expect(blockHeaders).not.toBeNull(); expect(blockHeaders.length).toBe(2); expect(blockHeaders.filter((header) => header.canonical).length).toBe(1); @@ -66,7 +67,7 @@ describe("api - beacon - getBlockHeaders", function () { .calledWith(0) .thenResolve({block: ssz.phase0.SignedBeaconBlock.defaultValue(), executionOptimistic: false, finalized: false}); when(modules.forkChoice.getBlockSummariesAtSlot).calledWith(0).thenReturn([]); - const {data: blockHeaders} = await api.getBlockHeaders({slot: 0}); + const {data: blockHeaders} = (await api.getBlockHeaders({slot: 0})) as {data: routes.beacon.BlockHeaderResponse[]}; expect(blockHeaders.length).toBe(1); expect(blockHeaders[0].canonical).toBe(true); }); @@ -91,7 +92,9 @@ describe("api - beacon - getBlockHeaders", function () { .thenReturn(generateProtoBlock({blockRoot: toHexString(ssz.phase0.BeaconBlock.hashTreeRoot(canonical.message))})); modules.db.block.get.mockResolvedValue(generateSignedBlockAtSlot(1)); modules.db.block.get.mockResolvedValue(generateSignedBlockAtSlot(2)); - const {data: blockHeaders} = await api.getBlockHeaders({parentRoot}); + const {data: blockHeaders} = (await api.getBlockHeaders({parentRoot})) as { + data: routes.beacon.BlockHeaderResponse[]; + }; expect(blockHeaders.length).toBe(3); expect(blockHeaders.filter((b) => b.canonical).length).toBe(2); }); diff --git a/packages/beacon-node/test/unit/api/impl/config/config.test.ts b/packages/beacon-node/test/unit/api/impl/config/config.test.ts index 5292d67393a3..d6954f632d5e 100644 --- a/packages/beacon-node/test/unit/api/impl/config/config.test.ts +++ b/packages/beacon-node/test/unit/api/impl/config/config.test.ts @@ -1,4 +1,5 @@ import {describe, it, expect, beforeEach} from "vitest"; +import {routes} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {getConfigApi, renderJsonSpec} from "../../../../../src/api/impl/config/index.js"; @@ -18,7 +19,7 @@ describe("config api implementation", function () { describe("getDepositContract", function () { it("should get the deposit contract from config", async function () { - const {data: depositContract} = await api.getDepositContract(); + const {data: depositContract} = (await api.getDepositContract()) as {data: routes.config.DepositContract}; expect(depositContract.address).toBe(config.DEPOSIT_CONTRACT_ADDRESS); expect(depositContract.chainId).toBe(config.DEPOSIT_CHAIN_ID); }); @@ -30,7 +31,7 @@ describe("config api implementation", function () { }); it("should get the spec", async function () { - const {data: specJson} = await api.getSpec(); + const {data: specJson} = (await api.getSpec()) as {data: routes.config.Spec}; expect(specJson.SECONDS_PER_ETH1_BLOCK).toBe("14"); expect(specJson.DEPOSIT_CONTRACT_ADDRESS).toBe("0x1234567890123456789012345678901234567890"); diff --git a/packages/beacon-node/test/unit/api/impl/events/events.test.ts b/packages/beacon-node/test/unit/api/impl/events/events.test.ts index 52ece27c4d5d..e031c3ac9958 100644 --- a/packages/beacon-node/test/unit/api/impl/events/events.test.ts +++ b/packages/beacon-node/test/unit/api/impl/events/events.test.ts @@ -43,8 +43,12 @@ describe("Events api impl", function () { function getEvents(topics: routes.events.EventType[]): routes.events.BeaconEvent[] { const events: routes.events.BeaconEvent[] = []; - void api.eventstream(topics, controller.signal, (event) => { - events.push(event); + void api.eventstream({ + topics, + signal: controller.signal, + onEvent: (event) => { + events.push(event); + }, }); return events; } 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 bf9544ad683e..d4f7705fb25b 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 @@ -1,4 +1,5 @@ import {describe, it, expect, beforeEach, afterEach, vi} from "vitest"; +import {routes} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {MAX_EFFECTIVE_BALANCE, SLOTS_PER_EPOCH} from "@lodestar/params"; import {BeaconStateAllForks} from "@lodestar/state-transition"; @@ -52,18 +53,18 @@ describe("get proposers api impl", function () { vi.advanceTimersByTime((SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH + 1) * config.SECONDS_PER_SLOT * 1000); vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.SyncingHead); - await expect(api.getProposerDuties(1)).rejects.toThrow("Node is syncing - headSlot 0 currentSlot 9"); + await expect(api.getProposerDuties({epoch: 1})).rejects.toThrow("Node is syncing - headSlot 0 currentSlot 9"); }); it("should raise error if node stalled", async () => { vi.advanceTimersByTime((SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH + 1) * config.SECONDS_PER_SLOT * 1000); vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.Stalled); - await expect(api.getProposerDuties(1)).rejects.toThrow("Node is syncing - waiting for peers"); + await expect(api.getProposerDuties({epoch: 1})).rejects.toThrow("Node is syncing - waiting for peers"); }); it("should get proposers for current epoch", async () => { - const {data: result} = await api.getProposerDuties(0); + const {data: result} = (await api.getProposerDuties({epoch: 0})) as {data: routes.validator.ProposerDutyList}; expect(result.length).toBe(SLOTS_PER_EPOCH); expect(cachedState.epochCtx.getBeaconProposers).toHaveBeenCalledOnce(); @@ -72,7 +73,7 @@ describe("get proposers api impl", function () { }); it("should get proposers for next epoch", async () => { - const {data: result} = await api.getProposerDuties(1); + const {data: result} = (await api.getProposerDuties({epoch: 1})) as {data: routes.validator.ProposerDutyList}; expect(result.length).toBe(SLOTS_PER_EPOCH); expect(cachedState.epochCtx.getBeaconProposers).not.toHaveBeenCalled(); @@ -81,27 +82,41 @@ describe("get proposers api impl", function () { }); it("should raise error for more than one epoch in the future", async () => { - await expect(api.getProposerDuties(2)).rejects.toThrow("Requested epoch 2 must equal current 0 or next epoch 1"); + await expect(api.getProposerDuties({epoch: 2})).rejects.toThrow( + "Requested epoch 2 must equal current 0 or next epoch 1" + ); }); it("should have different proposer validator public keys for current and next epoch", async () => { - const {data: currentProposers} = await api.getProposerDuties(0); - const {data: nextProposers} = await api.getProposerDuties(1); + const {data: currentProposers} = (await api.getProposerDuties({epoch: 0})) as { + data: routes.validator.ProposerDutyList; + }; + const {data: nextProposers} = (await api.getProposerDuties({epoch: 1})) as { + data: routes.validator.ProposerDutyList; + }; // Public keys should be different, but for tests we are generating a static list of validators with same public key expect(currentProposers.map((p) => p.pubkey)).toEqual(nextProposers.map((p) => p.pubkey)); }); it("should have different proposer validator indexes for current and next epoch", async () => { - const {data: currentProposers} = await api.getProposerDuties(0); - const {data: nextProposers} = await api.getProposerDuties(1); + const {data: currentProposers} = (await api.getProposerDuties({epoch: 0})) as { + data: routes.validator.ProposerDutyList; + }; + const {data: nextProposers} = (await api.getProposerDuties({epoch: 1})) as { + data: routes.validator.ProposerDutyList; + }; expect(currentProposers.map((p) => p.validatorIndex)).not.toEqual(nextProposers.map((p) => p.validatorIndex)); }); it("should have different proposer slots for current and next epoch", async () => { - const {data: currentProposers} = await api.getProposerDuties(0); - const {data: nextProposers} = await api.getProposerDuties(1); + const {data: currentProposers} = (await api.getProposerDuties({epoch: 0})) as { + data: routes.validator.ProposerDutyList; + }; + const {data: nextProposers} = (await api.getProposerDuties({epoch: 1})) as { + data: routes.validator.ProposerDutyList; + }; expect(currentProposers.map((p) => p.slot)).not.toEqual(nextProposers.map((p) => p.slot)); }); 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 9c426c677974..256d772d5fc0 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 @@ -21,7 +21,7 @@ describe("api - validator - produceAttestationData", function () { vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.SyncingFinalized); modules.forkChoice.getHead.mockReturnValue({slot: headSlot} as ProtoBlock); - await expect(api.produceAttestationData(0, 0)).rejects.toThrow("Node is syncing"); + await expect(api.produceAttestationData({committeeIndex: 0, slot: 0})).rejects.toThrow("Node is syncing"); }); it("Should throw error when node is stopped", async function () { @@ -30,6 +30,8 @@ describe("api - validator - produceAttestationData", function () { vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.Stalled); // Should not allow any call to validator API - await expect(api.produceAttestationData(0, 0)).rejects.toThrow("Node is syncing - waiting for peers"); + await expect(api.produceAttestationData({committeeIndex: 0, slot: 0})).rejects.toThrow( + "Node is syncing - waiting for peers" + ); }); }); 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 370a25d7cc92..e1ca3a084647 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 @@ -58,7 +58,7 @@ describe("api/validator - produceBlockV2", function () { }); // check if expectedFeeRecipient is passed to produceBlock - await api.produceBlockV2(slot, randaoReveal, graffiti, {feeRecipient}); + await api.produceBlockV2({slot, randaoReveal, graffiti, feeRecipient}); expect(modules.chain.produceBlock).toBeCalledWith({ randaoReveal, graffiti: toGraffitiBuffer(graffiti), @@ -69,7 +69,7 @@ describe("api/validator - produceBlockV2", function () { // check that no feeRecipient is passed to produceBlock so that produceBlockBody will // pick it from beaconProposerCache - await api.produceBlockV2(slot, randaoReveal, graffiti); + await api.produceBlockV2({slot, randaoReveal, graffiti}); expect(modules.chain.produceBlock).toBeCalledWith({ randaoReveal, graffiti: toGraffitiBuffer(graffiti), @@ -116,7 +116,7 @@ describe("api/validator - produceBlockV2", function () { parentSlot: slot - 1, parentBlockRoot: fromHexString(ZERO_HASH_HEX), proposerIndex: 0, - proposerPubKey: Uint8Array.from(Buffer.alloc(32, 1)), + proposerPubKey: new Uint8Array(32).fill(1), }); expect(modules.chain["executionEngine"].notifyForkchoiceUpdate).toBeCalledWith( @@ -126,7 +126,7 @@ describe("api/validator - produceBlockV2", function () { ZERO_HASH_HEX, { timestamp: computeTimeAtSlot(modules.config, state.slot, state.genesisTime), - prevRandao: Uint8Array.from(Buffer.alloc(32, 0)), + prevRandao: new Uint8Array(32), suggestedFeeRecipient: feeRecipient, } ); @@ -140,7 +140,7 @@ describe("api/validator - produceBlockV2", function () { parentSlot: slot - 1, parentBlockRoot: fromHexString(ZERO_HASH_HEX), proposerIndex: 0, - proposerPubKey: Uint8Array.from(Buffer.alloc(32, 1)), + proposerPubKey: new Uint8Array(32).fill(1), }); expect(modules.chain["executionEngine"].notifyForkchoiceUpdate).toBeCalledWith( @@ -150,7 +150,7 @@ describe("api/validator - produceBlockV2", function () { ZERO_HASH_HEX, { timestamp: computeTimeAtSlot(modules.config, state.slot, state.genesisTime), - prevRandao: Uint8Array.from(Buffer.alloc(32, 0)), + prevRandao: new Uint8Array(32), suggestedFeeRecipient: "0x fee recipient address", } ); 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 4adb07cd154b..309d68a9c9ec 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 @@ -131,13 +131,19 @@ describe("api/validator - produceBlockV3", function () { feeRecipient, }; - const block = await api.produceBlockV3(slot, randaoReveal, graffiti, _skipRandaoVerification, produceBlockOpts); + const {data: block, meta} = await api.produceBlockV3({ + slot, + randaoReveal, + graffiti, + skipRandaoVerification: _skipRandaoVerification, + ...produceBlockOpts, + }); const expectedBlock = finalSelection === "builder" ? blindedBlock : fullBlock; const expectedExecution = finalSelection === "builder" ? true : false; - expect(block.data).toEqual(expectedBlock); - expect(block.executionPayloadBlinded).toEqual(expectedExecution); + expect(block).toEqual(expectedBlock); + expect(meta.executionPayloadBlinded).toEqual(expectedExecution); // check call counts if (builderSelection === routes.validator.BuilderSelection.ExecutionOnly) { diff --git a/packages/beacon-node/test/unit/chain/beaconProposerCache.ts b/packages/beacon-node/test/unit/chain/beaconProposerCache.ts index ac54a8c841b0..4545ef0c94b2 100644 --- a/packages/beacon-node/test/unit/chain/beaconProposerCache.ts +++ b/packages/beacon-node/test/unit/chain/beaconProposerCache.ts @@ -8,30 +8,30 @@ describe("BeaconProposerCache", function () { beforeEach(function () { // max 2 items cache = new BeaconProposerCache({suggestedFeeRecipient}); - cache.add(1, {validatorIndex: "23", feeRecipient: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}); - cache.add(3, {validatorIndex: "43", feeRecipient: "0xcccccccccccccccccccccccccccccccccccccccc"}); + cache.add(1, {validatorIndex: 23, feeRecipient: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}); + cache.add(3, {validatorIndex: 43, feeRecipient: "0xcccccccccccccccccccccccccccccccccccccccc"}); }); it("get default", function () { - expect(cache.get("32")).toBe("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + expect(cache.get(32)).toBe("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); }); it("get what has been set", function () { - expect(cache.get("23")).toBe("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + expect(cache.get(23)).toBe("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); }); it("override and get latest", function () { - cache.add(5, {validatorIndex: "23", feeRecipient: "0xdddddddddddddddddddddddddddddddddddddddd"}); - expect(cache.get("23")).toBe("0xdddddddddddddddddddddddddddddddddddddddd"); + cache.add(5, {validatorIndex: 23, feeRecipient: "0xdddddddddddddddddddddddddddddddddddddddd"}); + expect(cache.get(23)).toBe("0xdddddddddddddddddddddddddddddddddddddddd"); }); it("prune", function () { cache.prune(4); // Default for what has been pruned - expect(cache.get("23")).toBe("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + expect(cache.get(23)).toBe("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // Original for what hasn't been pruned - expect(cache.get("43")).toBe("0xcccccccccccccccccccccccccccccccccccccccc"); + expect(cache.get(43)).toBe("0xcccccccccccccccccccccccccccccccccccccccc"); }); }); diff --git a/packages/beacon-node/test/utils/node/validator.ts b/packages/beacon-node/test/utils/node/validator.ts index ce81fc6d18ca..c686449a29f2 100644 --- a/packages/beacon-node/test/utils/node/validator.ts +++ b/packages/beacon-node/test/utils/node/validator.ts @@ -3,7 +3,8 @@ import type {SecretKey} from "@chainsafe/bls/types"; import {LevelDbController} from "@lodestar/db"; import {interopSecretKey} from "@lodestar/state-transition"; import {SlashingProtection, Validator, Signer, SignerType, ValidatorProposerConfig} from "@lodestar/validator"; -import {ServerApi, Api, HttpStatusCode, APIServerHandler} from "@lodestar/api"; +import {ApiClient, ApiError, HttpStatusCode, ApiResponse} from "@lodestar/api"; +import {BeaconApiMethods} from "@lodestar/api/beacon/server"; import {mapValues} from "@lodestar/utils"; import {BeaconNode} from "../../../src/index.js"; import {testLogger, TestLoggerOpts} from "../logger.js"; @@ -67,7 +68,9 @@ export async function getAndInitDevValidators({ Validator.initializeFromBeaconNode({ db, config: node.config, - api: useRestApi ? getNodeApiUrl(node) : getApiFromServerHandlers(node.api), + api: { + clientOrUrls: useRestApi ? getNodeApiUrl(node) : getApiFromServerHandlers(node.api), + }, slashingProtection, logger, processShutdownCallback: () => {}, @@ -87,38 +90,30 @@ export async function getAndInitDevValidators({ }; } -export function getApiFromServerHandlers(api: {[K in keyof Api]: ServerApi}): Api { +export function getApiFromServerHandlers(api: BeaconApiMethods): ApiClient { return mapValues(api, (apiModule) => - mapValues(apiModule, (api: APIServerHandler) => { - return async (...args: any) => { - let code: HttpStatusCode = HttpStatusCode.OK; + mapValues(apiModule, (api: (args: unknown, context: unknown) => PromiseLike<{data: unknown; meta: unknown}>) => { + return async (args: unknown) => { try { - const response = await api( - ...args, - // request object - {}, - // response object - { - code: (i: number) => { - code = i; - }, - } - ); - return {response, ok: true, status: code}; + const apiResponse = new ApiResponse({} as any, null, new Response(null, {status: HttpStatusCode.OK})); + const result = await api(args, {}); + apiResponse.value = () => result.data; + apiResponse.meta = () => result.meta; + return apiResponse; } catch (err) { - return { - ok: false, - status: code ?? HttpStatusCode.INTERNAL_SERVER_ERROR, - error: { - code: code ?? HttpStatusCode.INTERNAL_SERVER_ERROR, - message: (err as Error).message, - operationId: api.name, - }, + const apiResponse = new ApiResponse( + {} as any, + null, + new Response(null, {status: HttpStatusCode.INTERNAL_SERVER_ERROR}) + ); + apiResponse.error = () => { + return new ApiError((err as Error).message, HttpStatusCode.INTERNAL_SERVER_ERROR, api.name); }; + return apiResponse; } }; }) - ) as Api; + ) as ApiClient; } export function getNodeApiUrl(node: BeaconNode): string { diff --git a/packages/cli/src/cmds/lightclient/handler.ts b/packages/cli/src/cmds/lightclient/handler.ts index 04c833af92d5..a8e3d7333f98 100644 --- a/packages/cli/src/cmds/lightclient/handler.ts +++ b/packages/cli/src/cmds/lightclient/handler.ts @@ -1,6 +1,6 @@ import path from "node:path"; import {fromHexString} from "@chainsafe/ssz"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {Lightclient} from "@lodestar/light-client"; import {LightClientRestTransport} from "@lodestar/light-client/transport"; import {getNodeLogger} from "@lodestar/logger/node"; @@ -19,15 +19,14 @@ export async function lightclientHandler(args: ILightClientArgs & GlobalArgs): P ); const api = getClient({baseUrl: args.beaconApiUrl}, {config}); - const res = await api.beacon.getGenesis(); - ApiError.assert(res, "Can not fetch genesis data"); + const {genesisTime, genesisValidatorsRoot} = (await api.beacon.getGenesis()).value(); const client = await Lightclient.initializeFromCheckpointRoot({ config, logger, genesisData: { - genesisTime: Number(res.response.data.genesisTime), - genesisValidatorsRoot: res.response.data.genesisValidatorsRoot, + genesisTime, + genesisValidatorsRoot, }, checkpointRoot: fromHexString(args.checkpointRoot), transport: new LightClientRestTransport(api), diff --git a/packages/cli/src/cmds/validator/blsToExecutionChange.ts b/packages/cli/src/cmds/validator/blsToExecutionChange.ts index 7452840e1c71..960662b108ea 100644 --- a/packages/cli/src/cmds/validator/blsToExecutionChange.ts +++ b/packages/cli/src/cmds/validator/blsToExecutionChange.ts @@ -5,7 +5,7 @@ import {computeSigningRoot} from "@lodestar/state-transition"; import {DOMAIN_BLS_TO_EXECUTION_CHANGE, ForkName} from "@lodestar/params"; import {createBeaconConfig} from "@lodestar/config"; import {ssz, capella} from "@lodestar/types"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {CliCommand} from "@lodestar/utils"; import {GlobalArgs} from "../../options/index.js"; @@ -59,16 +59,12 @@ like to choose for BLS To Execution Change.", // submitting the signed message const {config: chainForkConfig} = getBeaconConfigFromArgs(args); const client = getClient({urls: args.beaconNodes}, {config: chainForkConfig}); - const genesisRes = await client.beacon.getGenesis(); - ApiError.assert(genesisRes, "Can not fetch genesis data"); - const {genesisValidatorsRoot} = genesisRes.response.data; + const {genesisValidatorsRoot} = (await client.beacon.getGenesis()).value(); const config = createBeaconConfig(chainForkConfig, genesisValidatorsRoot); - const stateValidatorRes = await client.beacon.getStateValidators("head", {id: [publicKey]}); - ApiError.assert(stateValidatorRes, "Can not fetch state validators"); - const stateValidators = stateValidatorRes.response.data; - const stateValidator = stateValidators[0]; - if (stateValidator === undefined) { + const validators = (await client.beacon.getStateValidators({stateId: "head", validatorIds: [publicKey]})).value(); + const validator = validators[0]; + if (validator === undefined) { throw new Error(`Validator pubkey ${publicKey} not found in state`); } @@ -76,7 +72,7 @@ like to choose for BLS To Execution Change.", const fromBlsPubkey = blsPrivkey.toPublicKey().toBytes(PointFormat.compressed); const blsToExecutionChange: capella.BLSToExecutionChange = { - validatorIndex: stateValidator.index, + validatorIndex: validator.index, fromBlsPubkey, toExecutionAddress: fromHexString(args.toExecutionAddress), }; @@ -89,10 +85,12 @@ like to choose for BLS To Execution Change.", signature: blsPrivkey.sign(signingRoot).toBytes(), }; - ApiError.assert( - await client.beacon.submitPoolBlsToExecutionChange([signedBLSToExecutionChange]), - "Can not submit bls to execution change" - ); + ( + await client.beacon.submitPoolBLSToExecutionChange({ + blsToExecutionChanges: [signedBLSToExecutionChange], + }) + ).assertOk(); + console.log(`Submitted bls to execution change for ${publicKey}`); }, }; diff --git a/packages/cli/src/cmds/validator/handler.ts b/packages/cli/src/cmds/validator/handler.ts index 80b1040f226a..fdd93f1ebfc9 100644 --- a/packages/cli/src/cmds/validator/handler.ts +++ b/packages/cli/src/cmds/validator/handler.ts @@ -8,7 +8,7 @@ import { ValidatorProposerConfig, defaultOptions, } from "@lodestar/validator"; -import {routes} from "@lodestar/api"; +import {WireFormat, routes} from "@lodestar/api"; import {getMetrics} from "@lodestar/validator"; import { RegistryMetricCreator, @@ -152,7 +152,13 @@ export async function validatorHandler(args: IValidatorCliArgs & GlobalArgs): Pr db, config, slashingProtection, - api: args.beaconNodes, + api: { + clientOrUrls: args.beaconNodes, + globalInit: { + requestWireFormat: parseWireFormat(args, "http.requestWireFormat"), + responseWireFormat: parseWireFormat(args, "http.responseWireFormat"), + }, + }, logger, processShutdownCallback, signers, @@ -269,3 +275,19 @@ function parseBroadcastValidation(broadcastValidation?: string): routes.beacon.B return broadcastValidation as routes.beacon.BroadcastValidation; } + +function parseWireFormat(args: IValidatorCliArgs, key: keyof IValidatorCliArgs): WireFormat | undefined { + const wireFormat = args[key]; + + if (wireFormat !== undefined) { + switch (wireFormat) { + case WireFormat.json: + case WireFormat.ssz: + break; + default: + throw new YargsError(`Invalid input for ${key}, must be one of "${WireFormat.json}" or "${WireFormat.ssz}"`); + } + } + + return wireFormat; +} diff --git a/packages/cli/src/cmds/validator/keymanager/impl.ts b/packages/cli/src/cmds/validator/keymanager/impl.ts index 3ff7e1af58a2..36ded66976b1 100644 --- a/packages/cli/src/cmds/validator/keymanager/impl.ts +++ b/packages/cli/src/cmds/validator/keymanager/impl.ts @@ -2,7 +2,6 @@ import bls from "@chainsafe/bls"; import {Keystore} from "@chainsafe/bls-keystore"; import {fromHexString} from "@chainsafe/ssz"; import { - Api as KeyManagerClientApi, DeleteRemoteKeyStatus, DeletionStatus, ImportStatus, @@ -11,10 +10,16 @@ import { PubkeyHex, SlashingProtectionData, SignerDefinition, + RemoteSignerDefinition, ImportRemoteKeyStatus, + FeeRecipientData, + GraffitiData, + GasLimitData, + BuilderBoostFactorData, } from "@lodestar/api/keymanager"; +import {KeymanagerApiMethods as Api} from "@lodestar/api/keymanager/server"; import {Interchange, SignerType, Validator} from "@lodestar/validator"; -import {ServerApi} from "@lodestar/api"; +import {ApiError} from "@lodestar/api/server"; import {Epoch} from "@lodestar/types"; import {isValidHttpUrl} from "@lodestar/utils"; import {getPubkeyHexFromKeystore, isValidatePubkeyHex} from "../../../util/format.js"; @@ -22,8 +27,6 @@ import {parseFeeRecipient} from "../../../util/index.js"; import {DecryptKeystoresThreadPool} from "./decryptKeystores/index.js"; import {IPersistedKeysBackend} from "./interface.js"; -type Api = ServerApi; - export class KeymanagerApi implements Api { constructor( private readonly validator: Validator, @@ -38,78 +41,61 @@ export class KeymanagerApi implements Api { } } - async listFeeRecipient(pubkeyHex: string): ReturnType { - return {data: {pubkey: pubkeyHex, ethaddress: this.validator.validatorStore.getFeeRecipient(pubkeyHex)}}; + async listFeeRecipient({pubkey}: {pubkey: PubkeyHex}): ReturnType { + return {data: {pubkey, ethaddress: this.validator.validatorStore.getFeeRecipient(pubkey)}}; } - async setFeeRecipient(pubkeyHex: string, ethaddress: string): Promise { + async setFeeRecipient({pubkey, ethaddress}: FeeRecipientData): ReturnType { this.checkIfProposerWriteEnabled(); - this.validator.validatorStore.setFeeRecipient(pubkeyHex, parseFeeRecipient(ethaddress)); - this.persistedKeysBackend.writeProposerConfig( - pubkeyHex, - this.validator.validatorStore.getProposerConfig(pubkeyHex) - ); + this.validator.validatorStore.setFeeRecipient(pubkey, parseFeeRecipient(ethaddress)); + this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey)); + return {status: 202}; } - async deleteFeeRecipient(pubkeyHex: string): Promise { + async deleteFeeRecipient({pubkey}: {pubkey: PubkeyHex}): ReturnType { this.checkIfProposerWriteEnabled(); - this.validator.validatorStore.deleteFeeRecipient(pubkeyHex); - this.persistedKeysBackend.writeProposerConfig( - pubkeyHex, - this.validator.validatorStore.getProposerConfig(pubkeyHex) - ); + this.validator.validatorStore.deleteFeeRecipient(pubkey); + this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey)); + return {status: 204}; } - async listGraffiti(pubkeyHex: string): ReturnType { - return {data: {pubkey: pubkeyHex, graffiti: this.validator.validatorStore.getGraffiti(pubkeyHex)}}; + async getGraffiti({pubkey}: {pubkey: PubkeyHex}): ReturnType { + return {data: {pubkey, graffiti: this.validator.validatorStore.getGraffiti(pubkey)}}; } - async setGraffiti(pubkeyHex: string, graffiti: string): Promise { + async setGraffiti({pubkey, graffiti}: GraffitiData): ReturnType { this.checkIfProposerWriteEnabled(); - this.validator.validatorStore.setGraffiti(pubkeyHex, graffiti); - this.persistedKeysBackend.writeProposerConfig( - pubkeyHex, - this.validator.validatorStore.getProposerConfig(pubkeyHex) - ); + this.validator.validatorStore.setGraffiti(pubkey, graffiti); + this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey)); + return {status: 202}; } - async deleteGraffiti(pubkeyHex: string): Promise { + async deleteGraffiti({pubkey}: {pubkey: PubkeyHex}): ReturnType { this.checkIfProposerWriteEnabled(); - this.validator.validatorStore.deleteGraffiti(pubkeyHex); - this.persistedKeysBackend.writeProposerConfig( - pubkeyHex, - this.validator.validatorStore.getProposerConfig(pubkeyHex) - ); + this.validator.validatorStore.deleteGraffiti(pubkey); + this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey)); + return {status: 204}; } - async getGasLimit(pubkeyHex: string): ReturnType { - const gasLimit = this.validator.validatorStore.getGasLimit(pubkeyHex); - return {data: {pubkey: pubkeyHex, gasLimit}}; + async getGasLimit({pubkey}: {pubkey: PubkeyHex}): ReturnType { + const gasLimit = this.validator.validatorStore.getGasLimit(pubkey); + return {data: {pubkey, gasLimit}}; } - async setGasLimit(pubkeyHex: string, gasLimit: number): Promise { + async setGasLimit({pubkey, gasLimit}: GasLimitData): ReturnType { this.checkIfProposerWriteEnabled(); - this.validator.validatorStore.setGasLimit(pubkeyHex, gasLimit); - this.persistedKeysBackend.writeProposerConfig( - pubkeyHex, - this.validator.validatorStore.getProposerConfig(pubkeyHex) - ); + this.validator.validatorStore.setGasLimit(pubkey, gasLimit); + this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey)); + return {status: 202}; } - async deleteGasLimit(pubkeyHex: string): Promise { + async deleteGasLimit({pubkey}: {pubkey: PubkeyHex}): ReturnType { this.checkIfProposerWriteEnabled(); - this.validator.validatorStore.deleteGasLimit(pubkeyHex); - this.persistedKeysBackend.writeProposerConfig( - pubkeyHex, - this.validator.validatorStore.getProposerConfig(pubkeyHex) - ); + this.validator.validatorStore.deleteGasLimit(pubkey); + this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey)); + return {status: 204}; } - /** - * List all validating pubkeys known to and decrypted by this keymanager binary - * - * https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml - */ async listKeys(): ReturnType { const pubkeys = this.validator.validatorStore.votingPubkeys(); return { @@ -121,43 +107,34 @@ export class KeymanagerApi implements Api { }; } - /** - * Import keystores generated by the Eth2.0 deposit CLI tooling. `passwords[i]` must unlock `keystores[i]`. - * - * Users SHOULD send slashing_protection data associated with the imported pubkeys. MUST follow the format defined in - * EIP-3076: Slashing Protection Interchange Format. - * - * @param keystoresStr JSON-encoded keystore files generated with the Launchpad - * @param passwords Passwords to unlock imported keystore files. `passwords[i]` must unlock `keystores[i]` - * @param slashingProtectionStr Slashing protection data for some of the keys of `keystores` - * @returns Status result of each `request.keystores` with same length and order of `request.keystores` - * - * https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml - */ - async importKeystores( - keystoresStr: KeystoreStr[], - passwords: string[], - slashingProtectionStr?: SlashingProtectionData - ): ReturnType { - if (slashingProtectionStr) { + async importKeystores({ + keystores, + passwords, + slashingProtection, + }: { + keystores: KeystoreStr[]; + passwords: string[]; + slashingProtection?: SlashingProtectionData; + }): ReturnType { + if (slashingProtection) { // The arguments to this function is passed in within the body of an HTTP request // hence fastify will parse it into an object before this function is called. - // Even though the slashingProtectionStr is typed as SlashingProtectionData, - // at runtime, when the handler for the request is selected, it would see slashingProtectionStr + // Even though the slashingProtection is typed as SlashingProtectionData, + // at runtime, when the handler for the request is selected, it would see slashingProtection // as an object, hence trying to parse it using JSON.parse won't work. Instead, we cast straight to Interchange - const interchange = ensureJSON(slashingProtectionStr); + const interchange = ensureJSON(slashingProtection); await this.validator.importInterchange(interchange); } const statuses: {status: ImportStatus; message?: string}[] = []; - const decryptKeystores = new DecryptKeystoresThreadPool(keystoresStr.length, this.signal); + const decryptKeystores = new DecryptKeystoresThreadPool(keystores.length, this.signal); - for (let i = 0; i < keystoresStr.length; i++) { + for (let i = 0; i < keystores.length; i++) { try { - const keystoreStr = keystoresStr[i]; + const keystoreStr = keystores[i]; const password = passwords[i]; if (password === undefined) { - throw Error(`No password for keystores[${i}]`); + throw new ApiError(400, `No password for keystores[${i}]`); } const keystore = Keystore.parse(keystoreStr); @@ -205,37 +182,16 @@ export class KeymanagerApi implements Api { return {data: statuses}; } - /** - * DELETE must delete all keys from `request.pubkeys` that are known to the keymanager and exist in its - * persistent storage. Additionally, DELETE must fetch the slashing protection data for the requested keys from - * persistent storage, which must be retained (and not deleted) after the response has been sent. Therefore in the - * case of two identical delete requests being made, both will have access to slashing protection data. - * - * In a single atomic sequential operation the keymanager must: - * 1. Guarantee that key(s) can not produce any more signature; only then - * 2. Delete key(s) and serialize its associated slashing protection data - * - * DELETE should never return a 404 response, even if all pubkeys from request.pubkeys have no extant keystores - * nor slashing protection data. - * - * Slashing protection data must only be returned for keys from `request.pubkeys` for which a - * `deleted` or `not_active` status is returned. - * - * @param pubkeysHex List of public keys to delete. - * @returns Deletion status of all keys in `request.pubkeys` in the same order. - * - * https://github.com/ethereum/keymanager-APIs/blob/0c975dae2ac6053c8245ebdb6a9f27c2f114f407/keymanager-oapi.yaml - */ - async deleteKeys(pubkeysHex: PubkeyHex[]): ReturnType { + async deleteKeys({pubkeys}: {pubkeys: PubkeyHex[]}): ReturnType { const deletedKey: boolean[] = []; - const statuses = new Array<{status: DeletionStatus; message?: string}>(pubkeysHex.length); + const statuses = new Array<{status: DeletionStatus; message?: string}>(pubkeys.length); - for (let i = 0; i < pubkeysHex.length; i++) { + for (let i = 0; i < pubkeys.length; i++) { try { - const pubkeyHex = pubkeysHex[i]; + const pubkeyHex = pubkeys[i]; if (!isValidatePubkeyHex(pubkeyHex)) { - throw Error(`Invalid pubkey ${pubkeyHex}`); + throw new ApiError(400, `Invalid pubkey ${pubkeyHex}`); } // Skip unknown keys or remote signers @@ -256,7 +212,7 @@ export class KeymanagerApi implements Api { const diskDeleteStatus = this.persistedKeysBackend.deleteKeystore(pubkeyHex); if (diskDeleteStatus) { - // TODO: What if the diskDeleteStatus status is incosistent? + // TODO: What if the diskDeleteStatus status is inconsistent? deletedKey[i] = true; } } catch (e) { @@ -264,7 +220,7 @@ export class KeymanagerApi implements Api { } } - const pubkeysBytes = pubkeysHex.map((pubkeyHex) => fromHexString(pubkeyHex)); + const pubkeysBytes = pubkeys.map((pubkeyHex) => fromHexString(pubkeyHex)); const interchangeV5 = await this.validator.exportInterchange(pubkeysBytes, { version: "5", @@ -272,27 +228,26 @@ export class KeymanagerApi implements Api { // After exporting slashing protection data in bulk, render the status const pubkeysWithSlashingProtectionData = new Set(interchangeV5.data.map((data) => data.pubkey)); - for (let i = 0; i < pubkeysHex.length; i++) { + for (let i = 0; i < pubkeys.length; i++) { if (statuses[i]?.status === DeletionStatus.error) { continue; } const status = deletedKey[i] ? DeletionStatus.deleted - : pubkeysWithSlashingProtectionData.has(pubkeysHex[i]) + : pubkeysWithSlashingProtectionData.has(pubkeys[i]) ? DeletionStatus.not_active : DeletionStatus.not_found; statuses[i] = {status}; } return { - data: statuses, - slashingProtection: JSON.stringify(interchangeV5), + data: { + statuses, + slashingProtection: JSON.stringify(interchangeV5), + }, }; } - /** - * List all remote validating pubkeys known to this validator client binary - */ async listRemoteKeys(): ReturnType { const remoteKeys: SignerDefinition[] = []; @@ -308,19 +263,18 @@ export class KeymanagerApi implements Api { }; } - /** - * Import remote keys for the validator client to request duties for - */ - async importRemoteKeys( - remoteSigners: Pick[] - ): ReturnType { + async importRemoteKeys({ + remoteSigners, + }: { + remoteSigners: RemoteSignerDefinition[]; + }): ReturnType { const importPromises = remoteSigners.map(async ({pubkey, url}): Promise> => { try { if (!isValidatePubkeyHex(pubkey)) { - throw Error(`Invalid pubkey ${pubkey}`); + throw new ApiError(400, `Invalid pubkey ${pubkey}`); } if (!isValidHttpUrl(url)) { - throw Error(`Invalid URL ${url}`); + throw new ApiError(400, `Invalid URL ${url}`); } // Check if key exists @@ -351,16 +305,11 @@ export class KeymanagerApi implements Api { }; } - /** - * DELETE must delete all keys from `request.pubkeys` that are known to the validator client and exist in its - * persistent storage. - * DELETE should never return a 404 response, even if all pubkeys from request.pubkeys have no existing keystores. - */ - async deleteRemoteKeys(pubkeys: PubkeyHex[]): ReturnType { + async deleteRemoteKeys({pubkeys}: {pubkeys: PubkeyHex[]}): ReturnType { const results = pubkeys.map((pubkeyHex): ResponseStatus => { try { if (!isValidatePubkeyHex(pubkeyHex)) { - throw Error(`Invalid pubkey ${pubkeyHex}`); + throw new ApiError(400, `Invalid pubkey ${pubkeyHex}`); } const signer = this.validator.validatorStore.getSigner(pubkeyHex); @@ -390,35 +339,31 @@ export class KeymanagerApi implements Api { }; } - async getBuilderBoostFactor(pubkeyHex: string): ReturnType { - const builderBoostFactor = this.validator.validatorStore.getBuilderBoostFactor(pubkeyHex); - return {data: {pubkey: pubkeyHex, builderBoostFactor}}; + async getBuilderBoostFactor({pubkey}: {pubkey: PubkeyHex}): ReturnType { + const builderBoostFactor = this.validator.validatorStore.getBuilderBoostFactor(pubkey); + return {data: {pubkey, builderBoostFactor}}; } - async setBuilderBoostFactor(pubkeyHex: string, builderBoostFactor: bigint): Promise { + async setBuilderBoostFactor({ + pubkey, + builderBoostFactor, + }: BuilderBoostFactorData): ReturnType { this.checkIfProposerWriteEnabled(); - this.validator.validatorStore.setBuilderBoostFactor(pubkeyHex, builderBoostFactor); - this.persistedKeysBackend.writeProposerConfig( - pubkeyHex, - this.validator.validatorStore.getProposerConfig(pubkeyHex) - ); + this.validator.validatorStore.setBuilderBoostFactor(pubkey, builderBoostFactor); + this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey)); + return {status: 202}; } - async deleteBuilderBoostFactor(pubkeyHex: string): Promise { + async deleteBuilderBoostFactor({pubkey}: {pubkey: PubkeyHex}): ReturnType { this.checkIfProposerWriteEnabled(); - this.validator.validatorStore.deleteBuilderBoostFactor(pubkeyHex); - this.persistedKeysBackend.writeProposerConfig( - pubkeyHex, - this.validator.validatorStore.getProposerConfig(pubkeyHex) - ); + this.validator.validatorStore.deleteBuilderBoostFactor(pubkey); + this.persistedKeysBackend.writeProposerConfig(pubkey, this.validator.validatorStore.getProposerConfig(pubkey)); + return {status: 204}; } - /** - * Create and sign a voluntary exit message for an active validator - */ - async signVoluntaryExit(pubkey: PubkeyHex, epoch?: Epoch): ReturnType { + async signVoluntaryExit({pubkey, epoch}: {pubkey: PubkeyHex; epoch?: Epoch}): ReturnType { if (!isValidatePubkeyHex(pubkey)) { - throw Error(`Invalid pubkey ${pubkey}`); + throw new ApiError(400, `Invalid pubkey ${pubkey}`); } return {data: await this.validator.signVoluntaryExit(pubkey, epoch)}; } diff --git a/packages/cli/src/cmds/validator/keymanager/server.ts b/packages/cli/src/cmds/validator/keymanager/server.ts index 6d2498dfbb2f..03880c8b8842 100644 --- a/packages/cli/src/cmds/validator/keymanager/server.ts +++ b/packages/cli/src/cmds/validator/keymanager/server.ts @@ -3,11 +3,8 @@ import fs from "node:fs"; import path from "node:path"; import {toHexString} from "@chainsafe/ssz"; import {RestApiServer, RestApiServerOpts, RestApiServerModules} from "@lodestar/beacon-node"; -import {Api} from "@lodestar/api/keymanager"; -import {registerRoutes} from "@lodestar/api/keymanager/server"; +import {KeymanagerApiMethods, registerRoutes} from "@lodestar/api/keymanager/server"; import {ChainForkConfig} from "@lodestar/config"; - -import {ServerApi} from "@lodestar/api"; import {writeFile600Perm} from "../../../util/index.js"; export type KeymanagerRestApiServerOpts = RestApiServerOpts & { @@ -28,7 +25,7 @@ export const keymanagerRestApiServerOptsDefault: KeymanagerRestApiServerOpts = { export type KeymanagerRestApiServerModules = RestApiServerModules & { config: ChainForkConfig; - api: ServerApi; + api: KeymanagerApiMethods; }; export const apiTokenFileName = "api-token.txt"; diff --git a/packages/cli/src/cmds/validator/options.ts b/packages/cli/src/cmds/validator/options.ts index d1603461e438..08548edb1072 100644 --- a/packages/cli/src/cmds/validator/options.ts +++ b/packages/cli/src/cmds/validator/options.ts @@ -1,3 +1,4 @@ +import {WireFormat, defaultInit} from "@lodestar/api"; import {defaultOptions} from "@lodestar/validator"; import {CliCommandOptions} from "@lodestar/utils"; import {LogArgs, logOptions} from "../../options/logOptions.js"; @@ -55,6 +56,9 @@ export type IValidatorCliArgs = AccountValidatorArgs & importKeystores?: string[]; importKeystoresPassword?: string; + "http.requestWireFormat"?: string; + "http.responseWireFormat"?: string; + "externalSigner.url"?: string; "externalSigner.pubkeys"?: string[]; "externalSigner.fetch"?: boolean; @@ -304,6 +308,20 @@ export const validatorOptions: CliCommandOptions = { type: "boolean", }, + "http.requestWireFormat": { + type: "string", + description: `Wire format to use in HTTP requests to beacon node. Can be one of \`${WireFormat.json}\` or \`${WireFormat.ssz}\``, + defaultDescription: `${defaultInit.requestWireFormat}`, + group: "http", + }, + + "http.responseWireFormat": { + type: "string", + description: `Preferred wire format for HTTP responses from beacon node. Can be one of \`${WireFormat.json}\` or \`${WireFormat.ssz}\``, + defaultDescription: `${defaultInit.responseWireFormat}`, + group: "http", + }, + // External signer "externalSigner.url": { diff --git a/packages/cli/src/cmds/validator/slashingProtection/utils.ts b/packages/cli/src/cmds/validator/slashingProtection/utils.ts index d81eb0b994e3..fe0af1cb2633 100644 --- a/packages/cli/src/cmds/validator/slashingProtection/utils.ts +++ b/packages/cli/src/cmds/validator/slashingProtection/utils.ts @@ -1,5 +1,5 @@ import {Root} from "@lodestar/types"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {fromHex, Logger} from "@lodestar/utils"; import {genesisData, NetworkName} from "@lodestar/config/networks"; import {SlashingProtection, MetaDataRepository} from "@lodestar/validator"; @@ -44,7 +44,7 @@ export async function getGenesisValidatorsRoot(args: GlobalArgs & ISlashingProte const genesis = await api.beacon.getGenesis(); try { - ApiError.assert(genesis, "Can not fetch genesis data"); + genesis.assertOk(); } catch (e) { if (args.force) { return Buffer.alloc(32, 0); @@ -52,5 +52,5 @@ export async function getGenesisValidatorsRoot(args: GlobalArgs & ISlashingProte throw e; } - return genesis.response.data.genesisValidatorsRoot; + return genesis.value().genesisValidatorsRoot; } diff --git a/packages/cli/src/cmds/validator/voluntaryExit.ts b/packages/cli/src/cmds/validator/voluntaryExit.ts index 89a908516f03..505abee1450c 100644 --- a/packages/cli/src/cmds/validator/voluntaryExit.ts +++ b/packages/cli/src/cmds/validator/voluntaryExit.ts @@ -10,7 +10,7 @@ import {createBeaconConfig, BeaconConfig} from "@lodestar/config"; import {phase0, ssz, ValidatorIndex, Epoch} from "@lodestar/types"; import {CliCommand, toHex} from "@lodestar/utils"; import {externalSignerPostSignature, SignableMessageType, Signer, SignerType} from "@lodestar/validator"; -import {Api, ApiError, getClient} from "@lodestar/api"; +import {ApiClient, getClient} from "@lodestar/api"; import {ensure0xPrefix, YargsError, wrapError} from "../../util/index.js"; import {GlobalArgs} from "../../options/index.js"; import {getBeaconConfigFromArgs} from "../../config/index.js"; @@ -75,9 +75,7 @@ If no `pubkeys` are provided, it will exit all validators that have been importe // Do not use known networks cache, it defaults to mainnet for devnets const {config: chainForkConfig, network} = getBeaconConfigFromArgs(args); const client = getClient({urls: args.beaconNodes}, {config: chainForkConfig}); - const genesisRes = await client.beacon.getGenesis(); - ApiError.assert(genesisRes, "Unable to fetch genesisValidatorsRoot from beacon node"); - const {genesisValidatorsRoot, genesisTime} = genesisRes.response.data; + const {genesisValidatorsRoot, genesisTime} = (await client.beacon.getGenesis()).value(); const config = createBeaconConfig(chainForkConfig, genesisValidatorsRoot); // Set exitEpoch to current epoch if unspecified @@ -143,7 +141,7 @@ ${validatorsToExit.map((v) => `${v.pubkey} ${v.index} ${v.status}`).join("\n")}` }; async function processVoluntaryExit( - {config, client}: {config: BeaconConfig; client: Api}, + {config, client}: {config: BeaconConfig; client: ApiClient}, exitEpoch: Epoch, validatorToExit: {index: ValidatorIndex; signer: Signer; pubkey: string} ): Promise { @@ -170,12 +168,12 @@ async function processVoluntaryExit( throw new YargsError(`Unexpected signer type for ${pubkey}`); } - ApiError.assert( - await client.beacon.submitPoolVoluntaryExit({ - message: voluntaryExit, - signature: signature.toBytes(), - }) - ); + const signedVoluntaryExit: phase0.SignedVoluntaryExit = { + message: voluntaryExit, + signature: signature.toBytes(), + }; + + (await client.beacon.submitPoolVoluntaryExit({signedVoluntaryExit})).assertOk(); } type SignerPubkey = {signer: Signer; pubkey: string}; @@ -206,13 +204,12 @@ function selectSignersToExit(args: VoluntaryExitArgs, signers: Signer[]): Signer } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -async function resolveValidatorIndexes(client: Api, signersToExit: SignerPubkey[]) { +async function resolveValidatorIndexes(client: ApiClient, signersToExit: SignerPubkey[]) { const pubkeys = signersToExit.map(({pubkey}) => pubkey); - const res = await client.beacon.getStateValidators("head", {id: pubkeys}); - ApiError.assert(res, "Can not fetch state validators from beacon node"); + const validators = (await client.beacon.getStateValidators({stateId: "head", validatorIds: pubkeys})).value(); - const dataByPubkey = new Map(res.response.data.map((item) => [toHex(item.validator.pubkey), item])); + const dataByPubkey = new Map(validators.map((item) => [toHex(item.validator.pubkey), item])); return signersToExit.map(({signer, pubkey}) => { const item = dataByPubkey.get(pubkey); diff --git a/packages/cli/src/networks/index.ts b/packages/cli/src/networks/index.ts index c7d4b5301062..c7a2c24186a6 100644 --- a/packages/cli/src/networks/index.ts +++ b/packages/cli/src/networks/index.ts @@ -2,11 +2,10 @@ import fs from "node:fs"; import got from "got"; import {ENR} from "@chainsafe/enr"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {ApiError, getClient} from "@lodestar/api"; -import {getStateTypeFromBytes} from "@lodestar/beacon-node"; +import {WireFormat, getClient} from "@lodestar/api"; import {ChainConfig, ChainForkConfig} from "@lodestar/config"; import {Checkpoint} from "@lodestar/types/phase0"; -import {Slot} from "@lodestar/types"; +import {Slot, ssz} from "@lodestar/types"; import {fromHex, callFnWhenAwait, Logger} from "@lodestar/utils"; import {BeaconStateAllForks, getLatestBlockRoot, computeCheckpointEpochAtStateSlot} from "@lodestar/state-transition"; import {parseBootnodesFile} from "../util/format.js"; @@ -174,19 +173,18 @@ export async function fetchWeakSubjectivityState( } // getStateV2 should be available for all forks including phase0 - const getStatePromise = api.debug.getStateV2(stateId, "ssz"); + const getStatePromise = api.debug.getStateV2({stateId}, {responseWireFormat: WireFormat.ssz}); - const stateBytes = await callFnWhenAwait( + const {stateBytes, fork} = await callFnWhenAwait( getStatePromise, () => logger.info("Download in progress, please wait..."), GET_STATE_LOG_INTERVAL ).then((res) => { - ApiError.assert(res, "Can not fetch state from beacon node"); - return res.response; + return {stateBytes: res.ssz(), fork: res.meta().version}; }); logger.info("Download completed", {stateId}); - const wsState = getStateTypeFromBytes(config, stateBytes).deserializeToViewDU(stateBytes); + const wsState = ssz.allForks[fork].BeaconState.deserializeToViewDU(stateBytes); return { wsState, diff --git a/packages/cli/test/e2e/blsToExecutionchange.test.ts b/packages/cli/test/e2e/blsToExecutionchange.test.ts index 31b4d76d8f00..b273ab90c996 100644 --- a/packages/cli/test/e2e/blsToExecutionchange.test.ts +++ b/packages/cli/test/e2e/blsToExecutionchange.test.ts @@ -2,7 +2,7 @@ import path from "node:path"; import {afterAll, describe, it, vi, beforeEach, afterEach} from "vitest"; import {toHexString} from "@chainsafe/ssz"; import {sleep, retry} from "@lodestar/utils"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {interopSecretKey} from "@lodestar/state-transition"; import {execCliCommand, spawnCliCommand, stopChildProcess} from "@lodestar/test-utils"; @@ -39,14 +39,13 @@ describe("bLSToExecutionChange cmd", function () { const baseUrl = `http://127.0.0.1:${restPort}`; // To cleanup the event stream connection const httpClientController = new AbortController(); - const client = getClient({baseUrl, getAbortSignal: () => httpClientController.signal}, {config}); + const client = getClient({baseUrl, globalInit: {signal: httpClientController.signal}}, {config}); // Wait for beacon node API to be available + genesis await retry( async () => { - const head = await client.beacon.getBlockHeader("head"); - ApiError.assert(head); - if (head.response.data.header.message.slot < 1) throw Error("pre-genesis"); + const head = (await client.beacon.getBlockHeader({blockId: "head"})).value(); + if (head.header.message.slot < 1) throw Error("pre-genesis"); }, {retryDelay: 1000, retries: 60} ); @@ -73,9 +72,8 @@ describe("bLSToExecutionChange cmd", function () { "--toExecutionAddress 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", ]); - const pooledBlsChanges = await client.beacon.getPoolBlsToExecutionChanges(); - ApiError.assert(pooledBlsChanges); - const message = pooledBlsChanges.response.data[0].message; + const pooledBlsChanges = (await client.beacon.getPoolBLSToExecutionChanges()).value(); + const {message} = pooledBlsChanges[0]; const {validatorIndex, toExecutionAddress, fromBlsPubkey} = message; if ( validatorIndex !== 0 || diff --git a/packages/cli/test/e2e/importKeystoresFromApi.test.ts b/packages/cli/test/e2e/importKeystoresFromApi.test.ts index 8733d6cd5c48..968e8ca980ce 100644 --- a/packages/cli/test/e2e/importKeystoresFromApi.test.ts +++ b/packages/cli/test/e2e/importKeystoresFromApi.test.ts @@ -4,7 +4,7 @@ import {rimraf} from "rimraf"; import {DeletionStatus, getClient, ImportStatus} from "@lodestar/api/keymanager"; import {config} from "@lodestar/config/default"; import {Interchange} from "@lodestar/validator"; -import {ApiError, HttpStatusCode} from "@lodestar/api"; +import {HttpStatusCode} from "@lodestar/api"; import {bufferStderr, spawnCliCommand} from "@lodestar/test-utils"; import {getKeystoresStr} from "@lodestar/test-utils"; import {testFilesDir} from "../utils.js"; @@ -63,10 +63,13 @@ describe("import keystores from api", function () { await expectKeys(keymanagerClient, [], "Wrong listKeys before importing"); // Import test keys - const importRes = await keymanagerClient.importKeystores(keystoresStr, passphrases, slashingProtectionStr); - ApiError.assert(importRes); + const importRes = await keymanagerClient.importKeystores({ + keystores: keystoresStr, + passwords: passphrases, + slashingProtection: slashingProtectionStr, + }); expectDeepEquals( - importRes.response.data, + importRes.value(), pubkeys.map(() => ({status: ImportStatus.imported})), "Wrong importKeystores response" ); @@ -75,10 +78,13 @@ describe("import keystores from api", function () { await expectKeys(keymanagerClient, pubkeys, "Wrong listKeys after importing"); // Attempt to import the same keys again - const importAgainRes = await keymanagerClient.importKeystores(keystoresStr, passphrases, slashingProtectionStr); - ApiError.assert(importAgainRes); + const importAgainRes = await keymanagerClient.importKeystores({ + keystores: keystoresStr, + passwords: passphrases, + slashingProtection: slashingProtectionStr, + }); expectDeepEquals( - importAgainRes.response.data, + importAgainRes.value(), pubkeys.map(() => ({status: ImportStatus.duplicate})), "Wrong importKeystores again response" ); @@ -117,10 +123,9 @@ describe("import keystores from api", function () { await expectKeys(keymanagerClient, pubkeys, "Wrong listKeys before deleting"); // Delete keys - const deleteRes = await keymanagerClient.deleteKeys(pubkeys); - ApiError.assert(deleteRes); + const deleteRes = await keymanagerClient.deleteKeys({pubkeys}); expectDeepEquals( - deleteRes.response.data, + deleteRes.value().statuses, pubkeys.map(() => ({status: DeletionStatus.deleted})), "Wrong deleteKeys response" ); @@ -138,9 +143,12 @@ describe("import keystores from api", function () { it("reject calls without bearerToken", async function () { await startValidatorWithKeyManager([], {dataDir}); - const keymanagerClientNoAuth = getClient({baseUrl: "http://localhost:38011", bearerToken: undefined}, {config}); + const keymanagerClientNoAuth = getClient( + {baseUrl: "http://localhost:38011", globalInit: {bearerToken: undefined}}, + {config} + ); const res = await keymanagerClientNoAuth.listRemoteKeys(); expect(res.ok).toBe(false); - expect(res.error?.code).toEqual(HttpStatusCode.UNAUTHORIZED); + expect(res.status).toEqual(HttpStatusCode.UNAUTHORIZED); }); }); diff --git a/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts b/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts index fd2193060ddd..c5638195d809 100644 --- a/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts +++ b/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts @@ -1,9 +1,9 @@ import path from "node:path"; import {describe, it, expect, beforeAll, vi} from "vitest"; import {rimraf} from "rimraf"; -import {Api, DeleteRemoteKeyStatus, getClient, ImportRemoteKeyStatus} from "@lodestar/api/keymanager"; +import {ApiClient, DeleteRemoteKeyStatus, getClient, ImportRemoteKeyStatus} from "@lodestar/api/keymanager"; import {config} from "@lodestar/config/default"; -import {ApiError, HttpStatusCode} from "@lodestar/api"; +import {HttpStatusCode} from "@lodestar/api"; import {testFilesDir} from "../utils.js"; import {cachedPubkeysHex} from "../utils/cachedKeys.js"; import {expectDeepEquals} from "../utils/runUtils.js"; @@ -11,11 +11,10 @@ import {startValidatorWithKeyManager} from "../utils/validator.js"; const url = "https://remote.signer"; -async function expectKeys(keymanagerClient: Api, expectedPubkeys: string[], message: string): Promise { - const remoteKeys = await keymanagerClient.listRemoteKeys(); - ApiError.assert(remoteKeys); +async function expectKeys(keymanagerClient: ApiClient, expectedPubkeys: string[], message: string): Promise { + const remoteKeys = (await keymanagerClient.listRemoteKeys()).value(); expectDeepEquals( - remoteKeys.response.data, + remoteKeys, expectedPubkeys.map((pubkey) => ({pubkey, url, readonly: false})), message ); @@ -40,10 +39,11 @@ describe("import remoteKeys from api", function () { await expectKeys(keymanagerClient, [], "Wrong listRemoteKeys before importing"); // Import test keys - const importRes = await keymanagerClient.importRemoteKeys(pubkeysToAdd.map((pubkey) => ({pubkey, url}))); - ApiError.assert(importRes); + const importRes = await keymanagerClient.importRemoteKeys({ + remoteSigners: pubkeysToAdd.map((pubkey) => ({pubkey, url})), + }); expectDeepEquals( - importRes.response.data, + importRes.value(), pubkeysToAdd.map(() => ({status: ImportRemoteKeyStatus.imported})), "Wrong importRemoteKeys response" ); @@ -52,10 +52,11 @@ describe("import remoteKeys from api", function () { await expectKeys(keymanagerClient, pubkeysToAdd, "Wrong listRemoteKeys after importing"); // Attempt to import the same keys again - const importAgainRes = await keymanagerClient.importRemoteKeys(pubkeysToAdd.map((pubkey) => ({pubkey, url}))); - ApiError.assert(importAgainRes); + const importAgainRes = await keymanagerClient.importRemoteKeys({ + remoteSigners: pubkeysToAdd.map((pubkey) => ({pubkey, url})), + }); expectDeepEquals( - importAgainRes.response.data, + importAgainRes.value(), pubkeysToAdd.map(() => ({status: ImportRemoteKeyStatus.duplicate})), "Wrong importRemoteKeys again response" ); @@ -67,10 +68,9 @@ describe("import remoteKeys from api", function () { await expectKeys(keymanagerClient, pubkeysToAdd, "Wrong listRemoteKeys before deleting"); // Delete keys - const deleteRes = await keymanagerClient.deleteRemoteKeys(pubkeysToAdd); - ApiError.assert(deleteRes); + const deleteRes = await keymanagerClient.deleteRemoteKeys({pubkeys: pubkeysToAdd}); expectDeepEquals( - deleteRes.response.data, + deleteRes.value(), pubkeysToAdd.map(() => ({status: DeleteRemoteKeyStatus.deleted})), "Wrong deleteRemoteKeys response" ); @@ -82,9 +82,9 @@ describe("import remoteKeys from api", function () { it("reject calls without bearerToken", async function () { await startValidatorWithKeyManager([], {dataDir}); const keymanagerUrl = "http://localhost:38011"; - const keymanagerClientNoAuth = getClient({baseUrl: keymanagerUrl, bearerToken: undefined}, {config}); + const keymanagerClientNoAuth = getClient({baseUrl: keymanagerUrl, globalInit: {bearerToken: undefined}}, {config}); const res = await keymanagerClientNoAuth.listRemoteKeys(); expect(res.ok).toBe(false); - expect(res.error?.code).toEqual(HttpStatusCode.UNAUTHORIZED); + expect(res.status).toEqual(HttpStatusCode.UNAUTHORIZED); }); }); diff --git a/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts b/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts index eff3a488c898..711997a86627 100644 --- a/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts +++ b/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts @@ -1,8 +1,8 @@ import path from "node:path"; import {describe, it, beforeAll, vi} from "vitest"; import {rimraf} from "rimraf"; +import {ImportStatus} from "@lodestar/api/keymanager"; import {Interchange} from "@lodestar/validator"; -import {ApiError} from "@lodestar/api"; import {getKeystoresStr} from "@lodestar/test-utils"; import {testFilesDir} from "../utils.js"; import {cachedPubkeysHex, cachedSeckeysHex} from "../utils/cachedKeys.js"; @@ -55,67 +55,56 @@ describe("import keystores from api, test DefaultProposerConfig", function () { // Produce and encrypt keystores // Import test keys const keystoresStr = await getKeystoresStr(passphrase, secretKeys); - await keymanagerClient.importKeystores(keystoresStr, passphrases, slashingProtectionStr); + const importRes = await keymanagerClient.importKeystores({ + keystores: keystoresStr, + passwords: passphrases, + slashingProtection: slashingProtectionStr, + }); + expectDeepEquals( + importRes.value(), + keystoresStr.map(() => ({status: ImportStatus.imported})), + "Wrong importKeystores response" + ); //////////////// Fee Recipient - let feeRecipient0 = await keymanagerClient.listFeeRecipient(pubkeys[0]); - ApiError.assert(feeRecipient0); + let feeRecipient0 = (await keymanagerClient.listFeeRecipient({pubkey: pubkeys[0]})).value(); expectDeepEquals( - feeRecipient0.response.data, + feeRecipient0, {pubkey: pubkeys[0], ethaddress: defaultOptions.suggestedFeeRecipient}, "FeeRecipient Check default" ); // Set feeClient to updatedOptions - ApiError.assert(await keymanagerClient.setFeeRecipient(pubkeys[0], updatedOptions.suggestedFeeRecipient)); - feeRecipient0 = await keymanagerClient.listFeeRecipient(pubkeys[0]); - ApiError.assert(feeRecipient0); + ( + await keymanagerClient.setFeeRecipient({pubkey: pubkeys[0], ethaddress: updatedOptions.suggestedFeeRecipient}) + ).assertOk(); + feeRecipient0 = (await keymanagerClient.listFeeRecipient({pubkey: pubkeys[0]})).value(); expectDeepEquals( - feeRecipient0.response.data, + feeRecipient0, {pubkey: pubkeys[0], ethaddress: updatedOptions.suggestedFeeRecipient}, "FeeRecipient Check updated" ); //////////////// Graffiti - let graffiti0 = await keymanagerClient.listGraffiti(pubkeys[0]); - ApiError.assert(graffiti0); - expectDeepEquals( - graffiti0.response.data, - {pubkey: pubkeys[0], graffiti: defaultOptions.graffiti}, - "Graffiti Check default" - ); + let graffiti0 = (await keymanagerClient.getGraffiti({pubkey: pubkeys[0]})).value(); + expectDeepEquals(graffiti0, {pubkey: pubkeys[0], graffiti: defaultOptions.graffiti}, "Graffiti Check default"); // Set Graffiti to updatedOptions - ApiError.assert(await keymanagerClient.setGraffiti(pubkeys[0], updatedOptions.graffiti)); - graffiti0 = await keymanagerClient.listGraffiti(pubkeys[0]); - ApiError.assert(graffiti0); - expectDeepEquals( - graffiti0.response.data, - {pubkey: pubkeys[0], graffiti: updatedOptions.graffiti}, - "FeeRecipient Check updated" - ); + (await keymanagerClient.setGraffiti({pubkey: pubkeys[0], graffiti: updatedOptions.graffiti})).assertOk(); + graffiti0 = (await keymanagerClient.getGraffiti({pubkey: pubkeys[0]})).value(); + expectDeepEquals(graffiti0, {pubkey: pubkeys[0], graffiti: updatedOptions.graffiti}, "FeeRecipient Check updated"); /////////// GasLimit - let gasLimit0 = await keymanagerClient.getGasLimit(pubkeys[0]); - ApiError.assert(gasLimit0); - expectDeepEquals( - gasLimit0.response.data, - {pubkey: pubkeys[0], gasLimit: defaultOptions.gasLimit}, - "gasLimit Check default" - ); + let gasLimit0 = (await keymanagerClient.getGasLimit({pubkey: pubkeys[0]})).value(); + expectDeepEquals(gasLimit0, {pubkey: pubkeys[0], gasLimit: defaultOptions.gasLimit}, "gasLimit Check default"); // Set GasLimit to updatedOptions - ApiError.assert(await keymanagerClient.setGasLimit(pubkeys[0], updatedOptions.gasLimit)); - gasLimit0 = await keymanagerClient.getGasLimit(pubkeys[0]); - ApiError.assert(gasLimit0); - expectDeepEquals( - gasLimit0.response.data, - {pubkey: pubkeys[0], gasLimit: updatedOptions.gasLimit}, - "gasLimit Check updated" - ); + (await keymanagerClient.setGasLimit({pubkey: pubkeys[0], gasLimit: updatedOptions.gasLimit})).assertOk(); + gasLimit0 = (await keymanagerClient.getGasLimit({pubkey: pubkeys[0]})).value(); + expectDeepEquals(gasLimit0, {pubkey: pubkeys[0], gasLimit: updatedOptions.gasLimit}, "gasLimit Check updated"); }); it("2 . run 'validator' Check last feeRecipient and gasLimit persists", async () => { @@ -124,57 +113,51 @@ describe("import keystores from api, test DefaultProposerConfig", function () { }); // next time check edited feeRecipient persists - let feeRecipient0 = await keymanagerClient.listFeeRecipient(pubkeys[0]); - ApiError.assert(feeRecipient0); + let feeRecipient0 = (await keymanagerClient.listFeeRecipient({pubkey: pubkeys[0]})).value(); expectDeepEquals( - feeRecipient0.response.data, + feeRecipient0, {pubkey: pubkeys[0], ethaddress: updatedOptions.suggestedFeeRecipient}, "FeeRecipient Check default persists" ); // after deletion feeRecipient restored to default - ApiError.assert(await keymanagerClient.deleteFeeRecipient(pubkeys[0])); - feeRecipient0 = await keymanagerClient.listFeeRecipient(pubkeys[0]); - ApiError.assert(feeRecipient0); + (await keymanagerClient.deleteFeeRecipient({pubkey: pubkeys[0]})).assertOk(); + feeRecipient0 = (await keymanagerClient.listFeeRecipient({pubkey: pubkeys[0]})).value(); expectDeepEquals( - feeRecipient0.response.data, + feeRecipient0, {pubkey: pubkeys[0], ethaddress: defaultOptions.suggestedFeeRecipient}, "FeeRecipient Check default after delete" ); // graffiti persists - let graffiti0 = await keymanagerClient.listGraffiti(pubkeys[0]); - ApiError.assert(graffiti0); + let graffiti0 = (await keymanagerClient.getGraffiti({pubkey: pubkeys[0]})).value(); expectDeepEquals( - graffiti0.response.data, + graffiti0, {pubkey: pubkeys[0], graffiti: updatedOptions.graffiti}, "FeeRecipient Check default persists" ); // after deletion graffiti restored to default - ApiError.assert(await keymanagerClient.deleteGraffiti(pubkeys[0])); - graffiti0 = await keymanagerClient.listGraffiti(pubkeys[0]); - ApiError.assert(graffiti0); + (await keymanagerClient.deleteGraffiti({pubkey: pubkeys[0]})).assertOk(); + graffiti0 = (await keymanagerClient.getGraffiti({pubkey: pubkeys[0]})).value(); expectDeepEquals( - graffiti0.response.data, + graffiti0, {pubkey: pubkeys[0], graffiti: defaultOptions.graffiti}, "FeeRecipient Check default after delete" ); // gasLimit persists - let gasLimit0 = await keymanagerClient.getGasLimit(pubkeys[0]); - ApiError.assert(gasLimit0); + let gasLimit0 = (await keymanagerClient.getGasLimit({pubkey: pubkeys[0]})).value(); expectDeepEquals( - gasLimit0.response.data, + gasLimit0, {pubkey: pubkeys[0], gasLimit: updatedOptions.gasLimit}, "gasLimit Check updated persists" ); - ApiError.assert(await keymanagerClient.deleteGasLimit(pubkeys[0])); - gasLimit0 = await keymanagerClient.getGasLimit(pubkeys[0]); - ApiError.assert(gasLimit0); + (await keymanagerClient.deleteGasLimit({pubkey: pubkeys[0]})).assertOk(); + gasLimit0 = (await keymanagerClient.getGasLimit({pubkey: pubkeys[0]})).value(); expectDeepEquals( - gasLimit0.response.data, + gasLimit0, {pubkey: pubkeys[0], gasLimit: defaultOptions.gasLimit}, "gasLimit Check default after delete" ); @@ -185,28 +168,25 @@ describe("import keystores from api, test DefaultProposerConfig", function () { dataDir, }); - const feeRecipient0 = await keymanagerClient.listFeeRecipient(pubkeys[0]); - ApiError.assert(feeRecipient0); + const feeRecipient0 = (await keymanagerClient.listFeeRecipient({pubkey: pubkeys[0]})).value(); expectDeepEquals( - feeRecipient0.response.data, + feeRecipient0, {pubkey: pubkeys[0], ethaddress: defaultOptions.suggestedFeeRecipient}, "FeeRecipient Check default persists" ); - ApiError.assert(await keymanagerClient.deleteGraffiti(pubkeys[0])); - const graffiti0 = await keymanagerClient.listGraffiti(pubkeys[0]); - ApiError.assert(graffiti0); + (await keymanagerClient.deleteGraffiti({pubkey: pubkeys[0]})).assertOk(); + const graffiti0 = (await keymanagerClient.getGraffiti({pubkey: pubkeys[0]})).value(); expectDeepEquals( - graffiti0.response.data, + graffiti0, {pubkey: pubkeys[0], graffiti: defaultOptions.graffiti}, "FeeRecipient Check default persists" ); - ApiError.assert(await keymanagerClient.deleteGasLimit(pubkeys[0])); - const gasLimit0 = await keymanagerClient.getGasLimit(pubkeys[0]); - ApiError.assert(gasLimit0); + (await keymanagerClient.deleteGasLimit({pubkey: pubkeys[0]})).assertOk(); + const gasLimit0 = (await keymanagerClient.getGasLimit({pubkey: pubkeys[0]})).value(); expectDeepEquals( - gasLimit0.response.data, + gasLimit0, {pubkey: pubkeys[0], gasLimit: defaultOptions.gasLimit}, "gasLimit Check default after delete" ); diff --git a/packages/cli/test/e2e/runDevCmd.test.ts b/packages/cli/test/e2e/runDevCmd.test.ts index c7f51b45045e..8e1b8b04e257 100644 --- a/packages/cli/test/e2e/runDevCmd.test.ts +++ b/packages/cli/test/e2e/runDevCmd.test.ts @@ -1,5 +1,5 @@ import {describe, it, vi, beforeEach, afterEach, afterAll} from "vitest"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {retry} from "@lodestar/utils"; import {spawnCliCommand} from "@lodestar/test-utils"; @@ -26,10 +26,10 @@ describe("Run dev command", function () { const beaconUrl = `http://127.0.0.1:${beaconPort}`; // To cleanup the event stream connection const httpClientController = new AbortController(); - const client = getClient({baseUrl: beaconUrl, getAbortSignal: () => httpClientController.signal}, {config}); + const client = getClient({baseUrl: beaconUrl, globalInit: {signal: httpClientController.signal}}, {config}); // Wrap in retry since the API may not be listening yet - await retry(() => client.node.getHealth().then((res) => ApiError.assert(res)), {retryDelay: 1000, retries: 60}); + await retry(() => client.node.getHealth().then((res) => res.assertOk()), {retryDelay: 1000, retries: 60}); httpClientController.abort(); // The process will exit when the test finishes diff --git a/packages/cli/test/e2e/voluntaryExit.test.ts b/packages/cli/test/e2e/voluntaryExit.test.ts index 89841fb7c3e4..f8c9150790f3 100644 --- a/packages/cli/test/e2e/voluntaryExit.test.ts +++ b/packages/cli/test/e2e/voluntaryExit.test.ts @@ -1,7 +1,7 @@ import path from "node:path"; import {afterAll, describe, it, vi, beforeEach, afterEach} from "vitest"; import {retry} from "@lodestar/utils"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {interopSecretKey} from "@lodestar/state-transition"; import {spawnCliCommand, execCliCommand} from "@lodestar/test-utils"; @@ -41,14 +41,13 @@ describe("voluntaryExit cmd", function () { const baseUrl = `http://127.0.0.1:${restPort}`; // To cleanup the event stream connection const httpClientController = new AbortController(); - const client = getClient({baseUrl, getAbortSignal: () => httpClientController.signal}, {config}); + const client = getClient({baseUrl, globalInit: {signal: httpClientController.signal}}, {config}); // Wait for beacon node API to be available + genesis await retry( async () => { - const head = await client.beacon.getBlockHeader("head"); - ApiError.assert(head); - if (head.response.data.header.message.slot < 1) throw Error("pre-genesis"); + const head = (await client.beacon.getBlockHeader({blockId: "head"})).value(); + if (head.header.message.slot < 1) throw Error("pre-genesis"); }, {retryDelay: 1000, retries: 20} ); @@ -79,13 +78,12 @@ describe("voluntaryExit cmd", function () { for (const pubkey of pubkeysToExit) { await retry( async () => { - const res = await client.beacon.getStateValidator("head", pubkey); - ApiError.assert(res); - if (res.response.data.status !== "active_exiting") { + const validator = (await client.beacon.getStateValidator({stateId: "head", validatorId: pubkey})).value(); + if (validator.status !== "active_exiting") { throw Error("Validator not exiting"); } else { // eslint-disable-next-line no-console - console.log(`Confirmed validator ${pubkey} = ${res.response.data.status}`); + console.log(`Confirmed validator ${pubkey} = ${validator.status}`); } }, {retryDelay: 1000, retries: 20} diff --git a/packages/cli/test/e2e/voluntaryExitFromApi.test.ts b/packages/cli/test/e2e/voluntaryExitFromApi.test.ts index 271f3d794ca3..ccd1dfeeba37 100644 --- a/packages/cli/test/e2e/voluntaryExitFromApi.test.ts +++ b/packages/cli/test/e2e/voluntaryExitFromApi.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; import {describe, it, vi, expect, afterAll, beforeEach, afterEach} from "vitest"; -import {ApiError, getClient} from "@lodestar/api"; +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"; @@ -53,9 +53,8 @@ describe("voluntary exit from api", function () { // Wait for beacon node API to be available + genesis await retry( async () => { - const head = await beaconClient.getBlockHeader("head"); - ApiError.assert(head); - if (head.response.data.header.message.slot < 1) throw Error("pre-genesis"); + const head = (await beaconClient.getBlockHeader({blockId: "head"})).value(); + if (head.header.message.slot < 1) throw Error("pre-genesis"); }, {retryDelay: 1000, retries: 20} ); @@ -65,9 +64,9 @@ describe("voluntary exit from api", function () { const indexToExit = 0; const pubkeyToExit = interopSecretKey(indexToExit).toPublicKey().toHex(); - const res = await keymanagerClient.signVoluntaryExit(pubkeyToExit, exitEpoch); - ApiError.assert(res); - const signedVoluntaryExit = res.response.data; + const signedVoluntaryExit = ( + await keymanagerClient.signVoluntaryExit({pubkey: pubkeyToExit, epoch: exitEpoch}) + ).value(); expect(signedVoluntaryExit.message.epoch).toBe(exitEpoch); expect(signedVoluntaryExit.message.validatorIndex).toBe(indexToExit); @@ -75,18 +74,17 @@ describe("voluntary exit from api", function () { expect(signedVoluntaryExit.signature).toBeDefined(); // 2. submit signed voluntary exit message to beacon node - ApiError.assert(await beaconClient.submitPoolVoluntaryExit(signedVoluntaryExit)); + (await beaconClient.submitPoolVoluntaryExit({signedVoluntaryExit})).assertOk(); // 3. confirm validator status is 'active_exiting' await retry( async () => { - const res = await beaconClient.getStateValidator("head", pubkeyToExit); - ApiError.assert(res); - if (res.response.data.status !== "active_exiting") { + const validator = (await beaconClient.getStateValidator({stateId: "head", validatorId: pubkeyToExit})).value(); + if (validator.status !== "active_exiting") { throw Error("Validator not exiting"); } else { // eslint-disable-next-line no-console - console.log(`Confirmed validator ${pubkeyToExit} = ${res.response.data.status}`); + console.log(`Confirmed validator ${pubkeyToExit} = ${validator.status}`); } }, {retryDelay: 1000, retries: 20} diff --git a/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts b/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts index b2f902c0e6dd..ab0c1e1e9ee3 100644 --- a/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts +++ b/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts @@ -1,7 +1,7 @@ import path from "node:path"; import {describe, it, beforeAll, afterAll, beforeEach, afterEach, vi} from "vitest"; import {retry} from "@lodestar/utils"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {interopSecretKey, interopSecretKeys} from "@lodestar/state-transition"; import { @@ -66,9 +66,8 @@ describe("voluntaryExit using remote signer", function () { // Wait for beacon node API to be available + genesis await retry( async () => { - const head = await client.beacon.getBlockHeader("head"); - ApiError.assert(head); - if (head.response.data.header.message.slot < 1) throw Error("pre-genesis"); + const head = (await client.beacon.getBlockHeader({blockId: "head"})).value(); + if (head.header.message.slot < 1) throw Error("pre-genesis"); }, {retryDelay: 1000, retries: 20} ); @@ -94,13 +93,12 @@ describe("voluntaryExit using remote signer", function () { for (const pubkey of pubkeysToExit) { await retry( async () => { - const res = await client.beacon.getStateValidator("head", pubkey); - ApiError.assert(res); - if (res.response.data.status !== "active_exiting") { + const validator = (await client.beacon.getStateValidator({stateId: "head", validatorId: pubkey})).value(); + if (validator.status !== "active_exiting") { throw Error("Validator not exiting"); } else { // eslint-disable-next-line no-console - console.log(`Confirmed validator ${pubkey} = ${res.response.data.status}`); + console.log(`Confirmed validator ${pubkey} = ${validator.status}`); } }, {retryDelay: 1000, retries: 20} diff --git a/packages/cli/test/sim/endpoints.test.ts b/packages/cli/test/sim/endpoints.test.ts index b58bece04d9d..a40a18e379eb 100644 --- a/packages/cli/test/sim/endpoints.test.ts +++ b/packages/cli/test/sim/endpoints.test.ts @@ -2,7 +2,7 @@ import path from "node:path"; import assert from "node:assert"; import {toHexString} from "@chainsafe/ssz"; -import {ApiError, routes} from "@lodestar/api"; +import {routes} from "@lodestar/api"; import {Simulation} from "../utils/crucible/simulation.js"; import {BeaconClient, ExecutionClient} from "../utils/crucible/interfaces.js"; import {defineSimTestConfig, logFilesDir} from "../utils/crucible/utils/index.js"; @@ -40,20 +40,18 @@ await env.start({runTimeoutMs: estimatedTimeoutMs}); const node = env.nodes[0].beacon; await waitForSlot("Wait for 2 slots before checking endpoints", {env, slot: 2}); -const res = await node.api.beacon.getStateValidators("head"); -ApiError.assert(res); -const stateValidators = res.response.data; +const validators = (await node.api.beacon.getStateValidators({stateId: "head"})).value(); await env.tracker.assert("should have correct validators count called without filters", async () => { - assert.equal(stateValidators.length, validatorCount); + assert.equal(validators.length, validatorCount); }); await env.tracker.assert("should have correct validator index for first validator filters", async () => { - assert.equal(stateValidators[0].index, 0); + assert.equal(validators[0].index, 0); }); await env.tracker.assert("should have correct validator index for second validator filters", async () => { - assert.equal(stateValidators[1].index, 1); + assert.equal(validators[1].index, 1); }); await env.tracker.assert( @@ -62,14 +60,13 @@ await env.tracker.assert( const filterPubKey = "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"; - const res = await node.api.beacon.getStateValidators("head", { - id: [filterPubKey], - }); - ApiError.assert(res); + const res = await node.api.beacon.getStateValidators({stateId: "head", validatorIds: [filterPubKey]}); - assert.equal(res.response.data.length, 1); - assert.equal(res.response.executionOptimistic, false); - assert.equal(res.response.finalized, false); + assert.equal(res.value().length, 1); + + const {executionOptimistic, finalized} = res.meta(); + assert.equal(executionOptimistic, false); + assert.equal(finalized, false); } ); @@ -79,12 +76,9 @@ await env.tracker.assert( const filterPubKey = "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"; - const res = await node.api.beacon.getStateValidators("head", { - id: [filterPubKey], - }); - ApiError.assert(res); + const res = await node.api.beacon.getStateValidators({stateId: "head", validatorIds: [filterPubKey]}); - assert.equal(toHexString(res.response.data[0].validator.pubkey), filterPubKey); + assert.equal(toHexString(res.value()[0].validator.pubkey), filterPubKey); } ); @@ -93,10 +87,9 @@ await env.tracker.assert( async () => { const validatorIndex = 0; - const res = await node.api.beacon.getStateValidator("head", validatorIndex); - ApiError.assert(res); + const res = await node.api.beacon.getStateValidator({stateId: "head", validatorId: validatorIndex}); - assert.equal(res.response.data.index, validatorIndex); + assert.equal(res.value().index, validatorIndex); } ); @@ -106,10 +99,9 @@ await env.tracker.assert( const hexPubKey = "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"; - const res = await node.api.beacon.getStateValidator("head", hexPubKey); - ApiError.assert(res); + const res = await node.api.beacon.getStateValidator({stateId: "head", validatorId: hexPubKey}); - assert.equal(toHexString(res.response.data.validator.pubkey), hexPubKey); + assert.equal(toHexString(res.value().validator.pubkey), hexPubKey); } ); @@ -123,9 +115,8 @@ await env.tracker.assert("BN Not Synced", async () => { }; const res = await node.api.node.getSyncingStatus(); - ApiError.assert(res); - assert.deepEqual(res.response.data, expectedSyncStatus); + assert.deepEqual(res.value(), expectedSyncStatus); }); await env.tracker.assert("Return READY pre genesis", async () => { diff --git a/packages/cli/test/utils/crucible/assertions/blobsAssertion.ts b/packages/cli/test/utils/crucible/assertions/blobsAssertion.ts index 997008272685..dece5bc58ce8 100644 --- a/packages/cli/test/utils/crucible/assertions/blobsAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/blobsAssertion.ts @@ -1,5 +1,4 @@ import {randomBytes} from "node:crypto"; -import {ApiError} from "@lodestar/api"; import {fromHex, toHex} from "@lodestar/utils"; import {Assertion, Match, AssertionResult, NodePair} from "../interfaces.js"; import {EL_GENESIS_ACCOUNT, EL_GENESIS_SECRET_KEY, SIM_ENV_CHAIN_ID} from "../constants.js"; @@ -50,10 +49,9 @@ export function createBlobsAssertion( sentBlobs.push(...blobs.map((b) => fromHex(b))); } - const blobSideCars = await node.beacon.api.beacon.getBlobSidecars(slot); - ApiError.assert(blobSideCars); + const blobSideCars = (await node.beacon.api.beacon.getBlobSidecars({blockId: slot})).value(); - return blobSideCars.response.data.map((b) => b.blob); + return blobSideCars.map((b) => b.blob); }, assert: async ({store}) => { diff --git a/packages/cli/test/utils/crucible/assertions/defaults/attestationParticipationAssertion.ts b/packages/cli/test/utils/crucible/assertions/defaults/attestationParticipationAssertion.ts index e44cb6cf5629..01cd6d29d09a 100644 --- a/packages/cli/test/utils/crucible/assertions/defaults/attestationParticipationAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/defaults/attestationParticipationAssertion.ts @@ -1,4 +1,3 @@ -import {ApiError} from "@lodestar/api"; import {TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX} from "@lodestar/params"; import {isActiveValidator} from "@lodestar/state-transition"; import {altair} from "@lodestar/types"; @@ -26,9 +25,8 @@ export const attestationParticipationAssertion: Assertion< }, async capture({node, epoch}) { - const res = await node.beacon.api.debug.getStateV2("head"); - ApiError.assert(res); - const state = res.response.data as altair.BeaconState; + const res = await node.beacon.api.debug.getStateV2({stateId: "head"}); + const state = res.value() as altair.BeaconState; // Attestation to be computed at the end of epoch. At that time the "currentEpochParticipation" is all set to zero // and we have to use "previousEpochParticipation" instead. diff --git a/packages/cli/test/utils/crucible/assertions/defaults/connectedPeerCountAssertion.ts b/packages/cli/test/utils/crucible/assertions/defaults/connectedPeerCountAssertion.ts index d3f6fe6038ff..83f308e62789 100644 --- a/packages/cli/test/utils/crucible/assertions/defaults/connectedPeerCountAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/defaults/connectedPeerCountAssertion.ts @@ -1,4 +1,3 @@ -import {ApiError} from "@lodestar/api"; import {AssertionResult, Assertion} from "../../interfaces.js"; import {everySlotMatcher} from "../matchers.js"; @@ -6,9 +5,7 @@ export const connectedPeerCountAssertion: Assertion<"connectedPeerCount", number id: "connectedPeerCount", match: everySlotMatcher, async capture({node}) { - const res = await node.beacon.api.node.getPeerCount(); - ApiError.assert(res); - return res.response.data.connected; + return (await node.beacon.api.node.getPeerCount()).value().connected; }, async assert({nodes, slot, store}) { const errors: AssertionResult[] = []; diff --git a/packages/cli/test/utils/crucible/assertions/defaults/finalizedAssertion.ts b/packages/cli/test/utils/crucible/assertions/defaults/finalizedAssertion.ts index 44bd01dd865e..f03fc41eb8e9 100644 --- a/packages/cli/test/utils/crucible/assertions/defaults/finalizedAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/defaults/finalizedAssertion.ts @@ -1,4 +1,3 @@ -import {ApiError} from "@lodestar/api"; import {Slot} from "@lodestar/types"; import {AssertionResult, Assertion} from "../../interfaces.js"; import {everySlotMatcher} from "../matchers.js"; @@ -7,9 +6,8 @@ export const finalizedAssertion: Assertion<"finalized", Slot> = { id: "finalized", match: everySlotMatcher, async capture({node}) { - const finalized = await node.beacon.api.beacon.getBlockHeader("finalized"); - ApiError.assert(finalized); - return finalized.response.data.header.message.slot ?? 0; + const finalized = (await node.beacon.api.beacon.getBlockHeader({blockId: "finalized"})).value(); + return finalized.header.message.slot ?? 0; }, async assert({store, slot, clock, epoch}) { const errors: AssertionResult[] = []; diff --git a/packages/cli/test/utils/crucible/assertions/defaults/headAssertion.ts b/packages/cli/test/utils/crucible/assertions/defaults/headAssertion.ts index 9c95b7658144..6464067d5d7d 100644 --- a/packages/cli/test/utils/crucible/assertions/defaults/headAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/defaults/headAssertion.ts @@ -1,4 +1,3 @@ -import {ApiError} from "@lodestar/api"; import {RootHex, Slot} from "@lodestar/types"; import {toHexString} from "@lodestar/utils"; import {AssertionResult, Assertion} from "../../interfaces.js"; @@ -13,12 +12,11 @@ export const headAssertion: Assertion<"head", HeadSummary> = { id: "head", match: everySlotMatcher, async capture({node}) { - const head = await node.beacon.api.beacon.getBlockHeader("head"); - ApiError.assert(head); + const head = (await node.beacon.api.beacon.getBlockHeader({blockId: "head"})).value(); return { - blockRoot: toHexString(head.response.data.root), - slot: head.response.data.header.message.slot, + blockRoot: toHexString(head.root), + slot: head.header.message.slot, }; }, async assert({nodes, node, store, slot, dependantStores}) { diff --git a/packages/cli/test/utils/crucible/assertions/executionHeadAssertion.ts b/packages/cli/test/utils/crucible/assertions/executionHeadAssertion.ts index 391fb27e6584..8ea0be1b445c 100644 --- a/packages/cli/test/utils/crucible/assertions/executionHeadAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/executionHeadAssertion.ts @@ -1,4 +1,3 @@ -import {ApiError} from "@lodestar/api"; import {toHex} from "@lodestar/utils"; import {bellatrix} from "@lodestar/types"; import {Match, AssertionResult, Assertion} from "../interfaces.js"; @@ -22,16 +21,13 @@ export function createExecutionHeadAssertion({ if (blockNumber == null) throw new Error("Execution provider not available"); const executionHeadBlock = await node.execution.provider?.eth.getBlock(blockNumber); - const consensusHead = await node.beacon.api.beacon.getBlockV2("head"); - ApiError.assert(consensusHead); + const consensusHead = (await node.beacon.api.beacon.getBlockV2({blockId: "head"})).value(); return { executionHead: {hash: executionHeadBlock?.hash ?? "0x0"}, consensusHead: { executionPayload: { - blockHash: toHex( - (consensusHead.response.data.message as bellatrix.BeaconBlock).body.executionPayload.blockHash - ), + blockHash: toHex((consensusHead.message as bellatrix.BeaconBlock).body.executionPayload.blockHash), }, }, }; diff --git a/packages/cli/test/utils/crucible/assertions/forkAssertion.ts b/packages/cli/test/utils/crucible/assertions/forkAssertion.ts index 5dc804c642f1..8f6372f856f6 100644 --- a/packages/cli/test/utils/crucible/assertions/forkAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/forkAssertion.ts @@ -1,4 +1,3 @@ -import {ApiError} from "@lodestar/api"; import {ForkName} from "@lodestar/params"; import {Epoch} from "@lodestar/types"; import {toHexString} from "@lodestar/utils"; @@ -15,10 +14,9 @@ export function createForkAssertion(fork: ForkName, epoch: Epoch): Assertion { const errors: AssertionResult[] = []; - const res = await node.beacon.api.debug.getStateV2("head"); - ApiError.assert(res); + const state = (await node.beacon.api.debug.getStateV2({stateId: "head"})).value(); const expectedForkVersion = toHexString(forkConfig.getForkInfo(slot).version); - const currentForkVersion = toHexString(res.response.data.fork.currentVersion); + const currentForkVersion = toHexString(state.fork.currentVersion); if (expectedForkVersion !== currentForkVersion) { errors.push([ diff --git a/packages/cli/test/utils/crucible/assertions/lighthousePeerScoreAssertion.ts b/packages/cli/test/utils/crucible/assertions/lighthousePeerScoreAssertion.ts index 1b6837a881e3..29fa36220b3d 100644 --- a/packages/cli/test/utils/crucible/assertions/lighthousePeerScoreAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/lighthousePeerScoreAssertion.ts @@ -1,4 +1,3 @@ -import {ApiError} from "@lodestar/api"; import {AssertionResult, BeaconClient, LighthouseAPI, NodePair, Assertion} from "../interfaces.js"; import {neverMatcher} from "./matchers.js"; @@ -47,13 +46,12 @@ export const lighthousePeerScoreAssertion: Assertion<"lighthousePeerScore", {gos }; async function getLodestarPeerIds(nodes: NodePair[]): Promise> { - const lodestartPeers = nodes.filter((n) => n.beacon.client === BeaconClient.Lodestar); + const lodestarPeers = nodes.filter((n) => n.beacon.client === BeaconClient.Lodestar); const peerIdMap: Record = {}; - for (const p of lodestartPeers) { - const res = await p.beacon.api.node.getNetworkIdentity(); - ApiError.assert(res); - peerIdMap[res.response.data.peerId] = p.beacon.id; + for (const p of lodestarPeers) { + const identity = (await p.beacon.api.node.getNetworkIdentity()).value(); + peerIdMap[identity.peerId] = p.beacon.id; } return peerIdMap; diff --git a/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts b/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts index d7a90857ae2c..acd686c87bf7 100644 --- a/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/mergeAssertion.ts @@ -1,4 +1,3 @@ -import {ApiError} from "@lodestar/api"; import {BeaconStateAllForks, isExecutionStateType, isMergeTransitionComplete} from "@lodestar/state-transition"; import {AssertionResult, Assertion} from "../interfaces.js"; import {neverMatcher} from "./matchers.js"; @@ -10,9 +9,8 @@ export const mergeAssertion: Assertion<"merge", string> = { async assert({node}) { const errors: AssertionResult[] = []; - const res = await node.beacon.api.debug.getStateV2("head"); - ApiError.assert(res); - const state = res.response.data as unknown as BeaconStateAllForks; + const res = await node.beacon.api.debug.getStateV2({stateId: "head"}); + const state = res.value() as unknown as BeaconStateAllForks; if (!(isExecutionStateType(state) && isMergeTransitionComplete(state))) { errors.push("Node has not yet completed the merged transition"); diff --git a/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts b/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts index 7a449b3f669f..69039accb182 100644 --- a/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts @@ -1,6 +1,5 @@ import type {SecretKey} from "@chainsafe/bls/types"; import {routes} from "@lodestar/api/beacon"; -import {ApiError} from "@lodestar/api"; import {AssertionResult, ValidatorClientKeys, Assertion, ValidatorClient} from "../interfaces.js"; import {arrayEquals} from "../utils/index.js"; import {neverMatcher} from "./matchers.js"; @@ -20,9 +19,8 @@ export const nodeAssertion: Assertion<"node", {health: number; keyManagerKeys: s if (node.validator.client == ValidatorClient.Lighthouse || getAllKeys(node.validator.keys).length === 0) { keyManagerKeys = []; } else { - const res = await node.validator.keyManager.listKeys(); - ApiError.assert(res); - keyManagerKeys = res.response.data.map((k) => k.validatingPubkey); + const keys = (await node.validator.keyManager.listKeys()).value(); + keyManagerKeys = keys.map((k) => k.validatingPubkey); } return {health, keyManagerKeys}; diff --git a/packages/cli/test/utils/crucible/assertions/withdrawalsAssertion.ts b/packages/cli/test/utils/crucible/assertions/withdrawalsAssertion.ts index 7daa38682829..be256a0b696d 100644 --- a/packages/cli/test/utils/crucible/assertions/withdrawalsAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/withdrawalsAssertion.ts @@ -1,5 +1,4 @@ import {capella} from "@lodestar/types"; -import {ApiError} from "@lodestar/api"; import {MAX_WITHDRAWALS_PER_PAYLOAD} from "@lodestar/params"; import {Match, AssertionResult, Assertion} from "../interfaces.js"; @@ -27,21 +26,23 @@ export function createWithdrawalAssertions( for (const withdrawal of withdrawals) { withdrawalAmount += withdrawal.amount; - const validatorDataLastSlot = await node.beacon.api.beacon.getStateValidator( - slot - 1, - withdrawal.validatorIndex - ); - const validatorDataCurrentSlot = await node.beacon.api.beacon.getStateValidator( - slot, - withdrawal.validatorIndex - ); - ApiError.assert(validatorDataLastSlot); - ApiError.assert(validatorDataCurrentSlot); + const validatorDataLastSlot = ( + await node.beacon.api.beacon.getStateValidator({ + stateId: slot - 1, + validatorId: withdrawal.validatorIndex, + }) + ).value(); + const validatorDataCurrentSlot = ( + await node.beacon.api.beacon.getStateValidator({ + stateId: slot, + validatorId: withdrawal.validatorIndex, + }) + ).value(); validators[withdrawal.validatorIndex] = { withdrawalAmount: withdrawal.amount, - balanceInLastSlot: BigInt(validatorDataLastSlot.response.data.balance), - currentBalance: BigInt(validatorDataCurrentSlot.response.data.balance), + balanceInLastSlot: BigInt(validatorDataLastSlot.balance), + currentBalance: BigInt(validatorDataCurrentSlot.balance), }; } diff --git a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts index 3e737416ddc1..500b93ee2fb7 100644 --- a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts +++ b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts @@ -2,7 +2,6 @@ import {writeFile} from "node:fs/promises"; import path from "node:path"; import got, {RequestError} from "got"; import yaml from "js-yaml"; -import {HttpClient} from "@lodestar/api"; import {getClient} from "@lodestar/api/beacon"; import {chainConfigToJson} from "@lodestar/config"; import {BeaconClient, BeaconNodeGenerator, LighthouseAPI, RunnerType} from "../../interfaces.js"; @@ -104,14 +103,15 @@ export const generateLighthouseBeaconNode: BeaconNodeGenerator & { +export type LodestarAPI = ApiClient; +export type LighthouseAPI = Omit & { lighthouse: { getPeers(): Promise<{ status: number; diff --git a/packages/cli/test/utils/crucible/simulation.ts b/packages/cli/test/utils/crucible/simulation.ts index 7a2343b6082d..b55ebf110c89 100644 --- a/packages/cli/test/utils/crucible/simulation.ts +++ b/packages/cli/test/utils/crucible/simulation.ts @@ -173,12 +173,12 @@ export class Simulation { for (const node of this.nodes) { if (node.validator?.keys.type === "remote") { this.externalSigner.addKeys(node.validator?.keys.secretKeys); - await node.validator.keyManager.importRemoteKeys( - node.validator.keys.secretKeys.map((sk) => ({ + await node.validator.keyManager.importRemoteKeys({ + remoteSigners: node.validator.keys.secretKeys.map((sk) => ({ pubkey: sk.toPublicKey().toHex(), url: this.externalSigner.url, - })) - ); + })), + }); this.logger.info(`Imported remote keys for node ${node.id}`); } } diff --git a/packages/cli/test/utils/crucible/simulationTracker.ts b/packages/cli/test/utils/crucible/simulationTracker.ts index 3533015d58d5..e374d3b6c328 100644 --- a/packages/cli/test/utils/crucible/simulationTracker.ts +++ b/packages/cli/test/utils/crucible/simulationTracker.ts @@ -431,19 +431,23 @@ export class SimulationTracker { signal?: AbortSignal ): void { debug("event stream initialized for", node.beacon.id); - void node.beacon.api.events.eventstream(events, signal ?? this.signal, async (event) => { - switch (event.type) { - case routes.events.EventType.block: - debug(`block received node=${node.beacon.id} slot=${event.message.slot}`); - await this.processOnBlock(event.message, node); - return; - case routes.events.EventType.head: - await this.processOnHead(event.message, node); - return; - case routes.events.EventType.finalizedCheckpoint: - this.processOnFinalizedCheckpoint(event.message, node); - return; - } + void node.beacon.api.events.eventstream({ + topics: events, + signal: signal ?? this.signal, + onEvent: async (event) => { + switch (event.type) { + case routes.events.EventType.block: + debug(`block received node=${node.beacon.id} slot=${event.message.slot}`); + await this.processOnBlock(event.message, node); + return; + case routes.events.EventType.head: + await this.processOnHead(event.message, node); + return; + case routes.events.EventType.finalizedCheckpoint: + this.processOnFinalizedCheckpoint(event.message, node); + return; + } + }, }); } } diff --git a/packages/cli/test/utils/crucible/utils/network.ts b/packages/cli/test/utils/crucible/utils/network.ts index b78087d8f931..6042e91bc221 100644 --- a/packages/cli/test/utils/crucible/utils/network.ts +++ b/packages/cli/test/utils/crucible/utils/network.ts @@ -1,5 +1,4 @@ /* eslint-disable no-console */ -import {ApiError} from "@lodestar/api"; import {Slot, allForks} from "@lodestar/types"; import {sleep} from "@lodestar/utils"; import {BeaconClient, BeaconNode, ExecutionClient, ExecutionNode, NodePair} from "../interfaces.js"; @@ -24,22 +23,23 @@ export async function connectNewNode(newNode: NodePair, nodes: NodePair[]): Prom } export async function connectNewCLNode(newNode: BeaconNode, nodes: BeaconNode[]): Promise { - const res = await newNode.api.node.getNetworkIdentity(); - ApiError.assert(res); - const clIdentity = res.response.data; + const clIdentity = (await newNode.api.node.getNetworkIdentity()).value(); if (!clIdentity.peerId) return; for (const node of nodes) { if (node === newNode) continue; if (node.client === BeaconClient.Lodestar) { - const res = await (node as BeaconNode).api.lodestar.connectPeer( - clIdentity.peerId, - // As the lodestar is always running on host - // convert the address to local host to connect the container node - clIdentity.p2pAddresses.map((str) => str.replace(/(\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/)/, "/127.0.0.1/")) - ); - ApiError.assert(res); + ( + await (node as BeaconNode).api.lodestar.connectPeer({ + peerId: clIdentity.peerId, + // As the lodestar is always running on host + // convert the address to local host to connect the container node + multiaddrs: clIdentity.p2pAddresses.map((str) => + str.replace(/(\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/)/, "/127.0.0.1/") + ), + }) + ).assertOk(); } } } @@ -76,9 +76,8 @@ export async function waitForNodeSync( export async function waitForNodeSyncStatus(env: Simulation, node: NodePair): Promise { // eslint-disable-next-line no-constant-condition while (true) { - const result = await node.beacon.api.node.getSyncingStatus(); - ApiError.assert(result); - if (!result.response.data.isSyncing) { + const result = (await node.beacon.api.node.getSyncingStatus()).value(); + if (!result.isSyncing) { break; } else { await sleep(1000, env.options.controller.signal); @@ -158,13 +157,13 @@ export async function fetchBlock( {tries, delay, slot, signal}: {slot: number; tries: number; delay: number; signal?: AbortSignal} ): Promise { for (let i = 0; i < tries; i++) { - const res = await node.beacon.api.beacon.getBlockV2(slot); + const res = await node.beacon.api.beacon.getBlockV2({blockId: slot}); if (!res.ok) { await sleep(delay, signal); continue; } - return res.response.data; + return res.value(); } return; diff --git a/packages/cli/test/utils/crucible/utils/syncing.ts b/packages/cli/test/utils/crucible/utils/syncing.ts index d56f7d7bb388..35b889fb3ac1 100644 --- a/packages/cli/test/utils/crucible/utils/syncing.ts +++ b/packages/cli/test/utils/crucible/utils/syncing.ts @@ -1,4 +1,4 @@ -import {ApiError, routes} from "@lodestar/api"; +import {routes} from "@lodestar/api"; import {Slot} from "@lodestar/types"; import {sleep, toHex} from "@lodestar/utils"; import type {Simulation} from "../simulation.js"; @@ -6,8 +6,7 @@ import {BeaconClient, ExecutionClient, NodePair} from "../interfaces.js"; import {connectNewCLNode, connectNewELNode, connectNewNode, waitForHead, waitForSlot} from "./network.js"; export async function assertRangeSync(env: Simulation): Promise { - const currentHead = await env.nodes[0].beacon.api.beacon.getBlockHeader("head"); - ApiError.assert(currentHead); + const currentHead = (await env.nodes[0].beacon.api.beacon.getBlockHeader({blockId: "head"})).value(); const rangeSync = await env.createNodePair({ id: "range-sync-node", @@ -45,8 +44,8 @@ export async function assertRangeSync(env: Simulation): Promise { ); await waitForNodeSync(env, rangeSync, { - head: toHex(currentHead.response.data.root), - slot: currentHead.response.data.header.message.slot, + head: toHex(currentHead.root), + slot: currentHead.header.message.slot, }); await rangeSync.beacon.job.stop(); @@ -63,8 +62,9 @@ export async function assertCheckpointSync(env: Simulation): Promise { }); } - const finalizedCheckpoint = await env.nodes[0].beacon.api.beacon.getStateFinalityCheckpoints("head"); - ApiError.assert(finalizedCheckpoint); + const finalizedCheckpoint = ( + await env.nodes[0].beacon.api.beacon.getStateFinalityCheckpoints({stateId: "head"}) + ).value(); const checkpointSync = await env.createNodePair({ id: "checkpoint-sync-node", @@ -72,7 +72,7 @@ export async function assertCheckpointSync(env: Simulation): Promise { type: BeaconClient.Lodestar, options: { clientOptions: { - wssCheckpoint: `${toHex(finalizedCheckpoint.response.data.finalized.root)}:${finalizedCheckpoint.response.data.finalized.epoch}`, + wssCheckpoint: `${toHex(finalizedCheckpoint.finalized.root)}:${finalizedCheckpoint.finalized.epoch}`, }, }, }, @@ -85,8 +85,8 @@ export async function assertCheckpointSync(env: Simulation): Promise { await connectNewNode(checkpointSync, env.nodes); await waitForNodeSync(env, checkpointSync, { - head: toHex(finalizedCheckpoint.response.data.finalized.root), - slot: env.clock.getLastSlotOfEpoch(finalizedCheckpoint.response.data.finalized.epoch), + head: toHex(finalizedCheckpoint.finalized.root), + slot: env.clock.getLastSlotOfEpoch(finalizedCheckpoint.finalized.epoch), }); await checkpointSync.beacon.job.stop(); @@ -94,10 +94,10 @@ export async function assertCheckpointSync(env: Simulation): Promise { } export async function assertUnknownBlockSync(env: Simulation): Promise { - const currentHead = await env.nodes[0].beacon.api.beacon.getBlockV2("head"); - ApiError.assert(currentHead); - const currentSidecars = await env.nodes[0].beacon.api.beacon.getBlobSidecars(currentHead.response.data.message.slot); - ApiError.assert(currentSidecars); + const currentHead = (await env.nodes[0].beacon.api.beacon.getBlockV2({blockId: "head"})).value(); + const currentSidecars = ( + await env.nodes[0].beacon.api.beacon.getBlobSidecars({blockId: currentHead.message.slot}) + ).value(); const unknownBlockSync = await env.createNodePair({ id: "unknown-block-sync-node", @@ -114,7 +114,7 @@ export async function assertUnknownBlockSync(env: Simulation): Promise { the 'unknown block sync' won't function properly. Moreover, the 'unknownBlockSync' requires some startup time, contributing to the overall gap. For stability in our CI, we've opted to set a higher limit on this constraint. */ - "sync.slotImportTolerance": currentHead.response.data.message.slot, + "sync.slotImportTolerance": currentHead.message.slot, }, }, }, @@ -128,18 +128,16 @@ export async function assertUnknownBlockSync(env: Simulation): Promise { // Wait for EL node to start and sync before publishing an unknown block await sleep(5000); try { - ApiError.assert( - await unknownBlockSync.beacon.api.beacon.publishBlockV2( - { - signedBlock: currentHead.response.data, - blobs: currentSidecars.response.data.map((b) => b.blob), - kzgProofs: currentSidecars.response.data.map((b) => b.kzgProof), + ( + await unknownBlockSync.beacon.api.beacon.publishBlockV2({ + signedBlockOrContents: { + signedBlock: currentHead, + blobs: currentSidecars.map((b) => b.blob), + kzgProofs: currentSidecars.map((b) => b.kzgProof), }, - { - broadcastValidation: routes.beacon.BroadcastValidation.none, - } - ) - ); + broadcastValidation: routes.beacon.BroadcastValidation.none, + }) + ).assertOk(); env.tracker.record({ message: "Publishing unknown block should fail", @@ -157,12 +155,8 @@ export async function assertUnknownBlockSync(env: Simulation): Promise { } await waitForHead(env, unknownBlockSync, { - head: toHex( - env.forkConfig - .getForkTypes(currentHead.response.data.message.slot) - .BeaconBlock.hashTreeRoot(currentHead.response.data.message) - ), - slot: currentHead.response.data.message.slot, + head: toHex(env.forkConfig.getForkTypes(currentHead.message.slot).BeaconBlock.hashTreeRoot(currentHead.message)), + slot: currentHead.message.slot, }); await unknownBlockSync.beacon.job.stop(); @@ -185,9 +179,8 @@ export async function waitForNodeSync( export async function waitForNodeSyncStatus(env: Simulation, node: NodePair): Promise { // eslint-disable-next-line no-constant-condition while (true) { - const result = await node.beacon.api.node.getSyncingStatus(); - ApiError.assert(result); - if (!result.response.data.isSyncing) { + const result = (await node.beacon.api.node.getSyncingStatus()).value(); + if (!result.isSyncing) { break; } else { await sleep(1000, env.options.controller.signal); diff --git a/packages/cli/test/utils/mockBeaconApiServer.ts b/packages/cli/test/utils/mockBeaconApiServer.ts index 9aea2c8d2857..80ce282e102c 100644 --- a/packages/cli/test/utils/mockBeaconApiServer.ts +++ b/packages/cli/test/utils/mockBeaconApiServer.ts @@ -1,6 +1,6 @@ import {RestApiServer, RestApiServerOpts, RestApiServerModules} from "@lodestar/beacon-node"; -import {registerRoutes} from "@lodestar/api/beacon/server"; -import {Api as ClientApi, allNamespaces, ServerApi} from "@lodestar/api"; +import {BeaconApiMethods, registerRoutes} from "@lodestar/api/beacon/server"; +import {allNamespaces} from "@lodestar/api"; import {ChainForkConfig} from "@lodestar/config"; import {config} from "@lodestar/config/default"; import {ssz} from "@lodestar/types"; @@ -10,7 +10,6 @@ import {testLogger} from "../../../beacon-node/test/utils/logger.js"; const ZERO_HASH_HEX = toHex(Buffer.alloc(32, 0)); -type Api = {[K in keyof ClientApi]: ServerApi}; type DeepPartial = { [K in keyof T]?: T[K] extends object ? DeepPartial : T[K]; }; @@ -20,7 +19,12 @@ export type MockBeaconApiOpts = { }; class MockBeaconRestApiServer extends RestApiServer { - constructor(optsArg: RestApiServerOpts, modules: RestApiServerModules, config: ChainForkConfig, api: Api) { + constructor( + optsArg: RestApiServerOpts, + modules: RestApiServerModules, + config: ChainForkConfig, + api: BeaconApiMethods + ) { super(optsArg, modules); // Instantiate and register the routes with matching namespace in `opts.api` @@ -70,10 +74,10 @@ export function getMockBeaconApiServer(opts: RestApiServerOpts, apiOpts?: MockBe // Do nothing }, }, - } as DeepPartial; + } as DeepPartial; const logger = testLogger("mock-beacon-api"); - const restApiServer = new MockBeaconRestApiServer(opts, {logger, metrics: null}, config, api as Api); + const restApiServer = new MockBeaconRestApiServer(opts, {logger, metrics: null}, config, api as BeaconApiMethods); return restApiServer; } diff --git a/packages/cli/test/utils/validator.ts b/packages/cli/test/utils/validator.ts index 62aad811bfe0..dc3ef754cc74 100644 --- a/packages/cli/test/utils/validator.ts +++ b/packages/cli/test/utils/validator.ts @@ -1,9 +1,8 @@ import childProcess from "node:child_process"; import {afterEach} from "vitest"; import {retry} from "@lodestar/utils"; -import {Api, getClient} from "@lodestar/api/keymanager"; +import {ApiClient, getClient} from "@lodestar/api/keymanager"; import {config} from "@lodestar/config/default"; -import {ApiError} from "@lodestar/api"; import {spawnCliCommand, gracefullyStopChildProcess} from "@lodestar/test-utils"; import {getMockBeaconApiServer} from "./mockBeaconApiServer.js"; import {expectDeepEqualsUnordered, findApiToken} from "./runUtils.js"; @@ -20,7 +19,7 @@ export async function startValidatorWithKeyManager( ): Promise<{ validator: childProcess.ChildProcessWithoutNullStreams; stopValidator: () => Promise; - keymanagerClient: Api; + keymanagerClient: ApiClient; }> { const keymanagerPort = 38011; const beaconPort = 39011; @@ -55,7 +54,7 @@ export async function startValidatorWithKeyManager( const apiToken = await retry(async () => findApiToken(dataDir), {retryDelay: 500, retries: 10}); const controller = new AbortController(); const keymanagerClient = getClient( - {baseUrl: keymanagerUrl, bearerToken: apiToken, getAbortSignal: () => controller.signal}, + {baseUrl: keymanagerUrl, globalInit: {bearerToken: apiToken, signal: controller.signal}}, {config} ); @@ -86,12 +85,15 @@ export async function startValidatorWithKeyManager( /** * Query `keymanagerClient.listKeys()` API endpoint and assert that expectedPubkeys are in the response */ -export async function expectKeys(keymanagerClient: Api, expectedPubkeys: string[], message: string): Promise { - const keys = await keymanagerClient.listKeys(); - ApiError.assert(keys); +export async function expectKeys( + keymanagerClient: ApiClient, + expectedPubkeys: string[], + message: string +): Promise { + const keys = (await keymanagerClient.listKeys()).value(); // The order of keys isn't always deterministic so we can't use deep equal expectDeepEqualsUnordered( - keys.response.data, + keys, expectedPubkeys.map((pubkey) => ({validatingPubkey: pubkey, derivationPath: "", readonly: false})), message ); diff --git a/packages/flare/src/cmds/selfSlashAttester.ts b/packages/flare/src/cmds/selfSlashAttester.ts index beaa14ed9291..048b62273d77 100644 --- a/packages/flare/src/cmds/selfSlashAttester.ts +++ b/packages/flare/src/cmds/selfSlashAttester.ts @@ -1,6 +1,6 @@ import bls from "@chainsafe/bls"; import type {SecretKey} from "@chainsafe/bls/types"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {phase0, ssz} from "@lodestar/types"; import {config as chainConfig} from "@lodestar/config/default"; import {createBeaconConfig, BeaconConfig} from "@lodestar/config"; @@ -62,10 +62,9 @@ export async function selfSlashAttesterHandler(args: SelfSlashArgs): Promise sk.toPublicKey().toHex()); - const res = await client.beacon.getStateValidators("head", {id: pksHex}); - ApiError.assert(res, "Can not fetch state validators from beacon node"); + const validators = (await client.beacon.getStateValidators({stateId: "head", validatorIds: pksHex})).value(); // All validators in the batch will be part of the same AttesterSlashing - const validators = res.response.data; const attestingIndices = validators.map((v) => v.index); // Submit all ProposerSlashing for range at once @@ -134,7 +131,7 @@ export async function selfSlashAttesterHandler(args: SelfSlashArgs): Promise sk.toPublicKey().toHex()); - const res = await client.beacon.getStateValidators("head", {id: pksHex}); - ApiError.assert(res, "Can not fetch state validators from beacon node"); - const validators = res.response.data; + const validators = (await client.beacon.getStateValidators({stateId: "head", validatorIds: pksHex})).value(); // Submit all ProposerSlashing for range at once await Promise.all( @@ -124,7 +121,7 @@ export async function selfSlashProposerHandler(args: SelfSlashArgs): Promise { - const res = await this.api.lightclient.getUpdates(startPeriod, count); - ApiError.assert(res); - return res.response; + const res = await this.api.lightclient.getLightClientUpdatesByRange({startPeriod, count}); + const updates = res.value(); + const {versions} = res.meta(); + return updates.map((data, i) => ({data, version: versions[i]})); } async getOptimisticUpdate(): Promise<{version: ForkName; data: allForks.LightClientOptimisticUpdate}> { - const res = await this.api.lightclient.getOptimisticUpdate(); - ApiError.assert(res); - return res.response; + const res = await this.api.lightclient.getLightClientOptimisticUpdate(); + return {version: res.meta().version, data: res.value()}; } async getFinalityUpdate(): Promise<{version: ForkName; data: allForks.LightClientFinalityUpdate}> { - const res = await this.api.lightclient.getFinalityUpdate(); - ApiError.assert(res); - return res.response; + const res = await this.api.lightclient.getLightClientFinalityUpdate(); + return {version: res.meta().version, data: res.value()}; } async getBootstrap(blockRoot: string): Promise<{version: ForkName; data: allForks.LightClientBootstrap}> { - const res = await this.api.lightclient.getBootstrap(blockRoot); - ApiError.assert(res); - return res.response; + const res = await this.api.lightclient.getLightClientBootstrap({blockRoot}); + return {version: res.meta().version, data: res.value()}; } async fetchBlock(blockRootAsString: string): Promise<{version: ForkName; data: allForks.SignedBeaconBlock}> { - const res = await this.api.beacon.getBlockV2(blockRootAsString); - ApiError.assert(res); - return res.response; + const res = await this.api.beacon.getBlockV2({blockId: blockRootAsString}); + return {version: res.meta().version, data: res.value()}; } onOptimisticUpdate(handler: (optimisticUpdate: allForks.LightClientOptimisticUpdate) => void): void { @@ -72,10 +69,10 @@ export class LightClientRestTransport implements LightClientTransport { return; } - void this.api.events.eventstream( - [routes.events.EventType.lightClientOptimisticUpdate, routes.events.EventType.lightClientFinalityUpdate], - this.controller.signal, - (event) => { + void this.api.events.eventstream({ + topics: [routes.events.EventType.lightClientOptimisticUpdate, routes.events.EventType.lightClientFinalityUpdate], + signal: this.controller.signal, + onEvent: (event) => { switch (event.type) { case routes.events.EventType.lightClientOptimisticUpdate: this.eventEmitter.emit(routes.events.EventType.lightClientOptimisticUpdate, event.message.data); @@ -85,8 +82,8 @@ export class LightClientRestTransport implements LightClientTransport { this.eventEmitter.emit(routes.events.EventType.lightClientFinalityUpdate, event.message.data); break; } - } - ); + }, + }); this.subscribedEventstream = true; } } diff --git a/packages/light-client/src/utils/api.ts b/packages/light-client/src/utils/api.ts index bcc8a4d4f29c..7947aa96dd3e 100644 --- a/packages/light-client/src/utils/api.ts +++ b/packages/light-client/src/utils/api.ts @@ -1,8 +1,8 @@ -import {getClient, Api} from "@lodestar/api"; +import {getClient, ApiClient} from "@lodestar/api"; import {ChainForkConfig, createChainForkConfig} from "@lodestar/config"; import {NetworkName, networksChainConfig} from "@lodestar/config/networks"; -export function getApiFromUrl(url: string, network: NetworkName): Api { +export function getApiFromUrl(url: string, network: NetworkName): ApiClient { if (!(network in networksChainConfig)) { throw Error(`Invalid network name "${network}". Valid options are: ${Object.keys(networksChainConfig).join()}`); } diff --git a/packages/light-client/src/utils/utils.ts b/packages/light-client/src/utils/utils.ts index b7c2c29319e3..5a298d81d37b 100644 --- a/packages/light-client/src/utils/utils.ts +++ b/packages/light-client/src/utils/utils.ts @@ -1,7 +1,7 @@ import bls from "@chainsafe/bls"; import type {PublicKey} from "@chainsafe/bls/types"; import {BitArray} from "@chainsafe/ssz"; -import {Api, ApiError} from "@lodestar/api"; +import {ApiClient} from "@lodestar/api"; import {altair, Bytes32, Root, ssz} from "@lodestar/types"; import {BeaconBlockHeader} from "@lodestar/types/phase0"; import {GenesisData} from "../index.js"; @@ -81,18 +81,15 @@ export function isEmptyHeader(header: BeaconBlockHeader): boolean { export const isNode = Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]"; -export async function getGenesisData(api: Pick): Promise { - const res = await api.beacon.getGenesis(); - ApiError.assert(res); +export async function getGenesisData(api: Pick): Promise { + const {genesisTime, genesisValidatorsRoot} = (await api.beacon.getGenesis()).value(); return { - genesisTime: res.response.data.genesisTime, - genesisValidatorsRoot: res.response.data.genesisValidatorsRoot, + genesisTime, + genesisValidatorsRoot, }; } -export async function getFinalizedSyncCheckpoint(api: Pick): Promise { - const res = await api.beacon.getStateFinalityCheckpoints("head"); - ApiError.assert(res); - return res.response.data.finalized.root; +export async function getFinalizedSyncCheckpoint(api: Pick): Promise { + return (await api.beacon.getStateFinalityCheckpoints({stateId: "head"})).value().finalized.root; } diff --git a/packages/light-client/test/mocks/EventsServerApiMock.ts b/packages/light-client/test/mocks/EventsServerApiMock.ts index 73c212a9c4ee..905f2f5baa36 100644 --- a/packages/light-client/test/mocks/EventsServerApiMock.ts +++ b/packages/light-client/test/mocks/EventsServerApiMock.ts @@ -1,11 +1,12 @@ -import {routes, ServerApi} from "@lodestar/api"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; type OnEvent = (event: routes.events.BeaconEvent) => void; /** * In-memory simple event emitter for `BeaconEvent` */ -export class EventsServerApiMock implements ServerApi { +export class EventsServerApiMock implements ApplicationMethods { private readonly onEventsByTopic = new Map>(); hasSubscriptions(): boolean { @@ -21,7 +22,15 @@ export class EventsServerApiMock implements ServerApi { } } - async eventstream(topics: routes.events.EventType[], signal: AbortSignal, onEvent: OnEvent): Promise { + async eventstream({ + topics, + signal, + onEvent, + }: { + topics: routes.events.EventType[]; + signal: AbortSignal; + onEvent: OnEvent; + }): Promise { for (const topic of typeof topics === "string" ? [topics] : topics) { let onEvents = this.onEventsByTopic.get(topic); if (!onEvents) { diff --git a/packages/light-client/test/mocks/LightclientServerApiMock.ts b/packages/light-client/test/mocks/LightclientServerApiMock.ts index 0b6676ccdac1..faf33ad260c8 100644 --- a/packages/light-client/test/mocks/LightclientServerApiMock.ts +++ b/packages/light-client/test/mocks/LightclientServerApiMock.ts @@ -1,68 +1,85 @@ import {concat} from "uint8arrays/concat"; import {digest} from "@chainsafe/as-sha256"; -import {createProof, Proof, ProofType} from "@chainsafe/persistent-merkle-tree"; -import {routes, ServerApi} from "@lodestar/api"; +import {CompactMultiProof, createProof, ProofType} from "@chainsafe/persistent-merkle-tree"; +import {routes} from "@lodestar/api"; +import {ApplicationMethods} from "@lodestar/api/server"; import {altair, RootHex, SyncPeriod} from "@lodestar/types"; import {notNullish} from "@lodestar/utils"; import {ForkName} from "@lodestar/params"; import {BeaconStateAltair} from "../utils/types.js"; -export class ProofServerApiMock implements ServerApi { +type ProofApi = ApplicationMethods; + +export class ProofServerApiMock implements ProofApi { readonly states = new Map(); - async getStateProof(stateId: string, descriptor: Uint8Array): Promise<{data: Proof}> { + async getStateProof({ + stateId, + descriptor, + }: { + stateId: string; + descriptor: Uint8Array; + }): ReturnType { const state = this.states.get(stateId); if (!state) throw Error(`stateId ${stateId} not available`); - return {data: createProof(state.node, {type: ProofType.compactMulti, descriptor})}; + const proof = createProof(state.node, {type: ProofType.compactMulti, descriptor}); + return {data: proof as CompactMultiProof, meta: {version: ForkName.bellatrix}}; } - async getBlockProof(blockId: string, _descriptor: Uint8Array): Promise<{data: Proof}> { + async getBlockProof({blockId}: {blockId: string}): ReturnType { throw Error(`blockId ${blockId} not available`); } } -type VersionedLightClientUpdate = { - version: ForkName; - data: altair.LightClientUpdate; -}; +type LightClientApi = ApplicationMethods; -export class LightclientServerApiMock implements ServerApi { +export class LightclientServerApiMock implements LightClientApi { readonly updates = new Map(); readonly snapshots = new Map(); latestHeadUpdate: altair.LightClientOptimisticUpdate | null = null; finalized: altair.LightClientFinalityUpdate | null = null; - async getUpdates(from: SyncPeriod, to: SyncPeriod): Promise { - const updates: VersionedLightClientUpdate[] = []; - for (let period = parseInt(String(from)); period <= parseInt(String(to)); period++) { + async getLightClientUpdatesByRange(args: { + startPeriod: SyncPeriod; + count: number; + }): ReturnType { + const updates: altair.LightClientUpdate[] = []; + for (let period = parseInt(String(args.startPeriod)); period <= parseInt(String(args.count)); period++) { const update = this.updates.get(period); if (update) { - updates.push({ - version: ForkName.bellatrix, - data: update, - }); + updates.push(update); } } - return updates; + return {data: updates, meta: {versions: Array.from({length: updates.length}, () => ForkName.bellatrix)}}; } - async getOptimisticUpdate(): Promise<{version: ForkName; data: altair.LightClientOptimisticUpdate}> { + async getLightClientOptimisticUpdate(): ReturnType { if (!this.latestHeadUpdate) throw Error("No latest head update"); - return {version: ForkName.bellatrix, data: this.latestHeadUpdate}; + return {data: this.latestHeadUpdate, meta: {version: ForkName.bellatrix}}; } - async getFinalityUpdate(): Promise<{version: ForkName; data: altair.LightClientFinalityUpdate}> { + async getLightClientFinalityUpdate(): ReturnType { if (!this.finalized) throw Error("No finalized head update"); - return {version: ForkName.bellatrix, data: this.finalized}; + return {data: this.finalized, meta: {version: ForkName.bellatrix}}; } - async getBootstrap(blockRoot: string): Promise<{version: ForkName; data: altair.LightClientBootstrap}> { + async getLightClientBootstrap({ + blockRoot, + }: { + blockRoot: string; + }): ReturnType { const snapshot = this.snapshots.get(blockRoot); if (!snapshot) throw Error(`snapshot for blockRoot ${blockRoot} not available`); - return {version: ForkName.bellatrix, data: snapshot}; + return {data: snapshot, meta: {version: ForkName.bellatrix}}; } - async getCommitteeRoot(startPeriod: SyncPeriod, count: number): Promise<{data: Uint8Array[]}> { + async getLightClientCommitteeRoot({ + startPeriod, + count, + }: { + startPeriod: SyncPeriod; + count: number; + }): ReturnType { const periods = Array.from({length: count}, (_ignored, i) => i + startPeriod); const committeeHashes = periods .map((period) => this.updates.get(period)?.nextSyncCommittee.pubkeys) diff --git a/packages/light-client/test/unit/sync.node.test.ts b/packages/light-client/test/unit/sync.node.test.ts index 4d4212d626cf..bcd1d0f25d0b 100644 --- a/packages/light-client/test/unit/sync.node.test.ts +++ b/packages/light-client/test/unit/sync.node.test.ts @@ -1,10 +1,10 @@ import {describe, it, expect, afterEach, vi} from "vitest"; import {JsonPath, toHexString} from "@chainsafe/ssz"; -import {computeDescriptor, TreeOffsetProof} from "@chainsafe/persistent-merkle-tree"; +import {CompactMultiProof, computeDescriptor} from "@chainsafe/persistent-merkle-tree"; import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/params"; import {BeaconStateAllForks, BeaconStateAltair} from "@lodestar/state-transition"; import {altair, ssz} from "@lodestar/types"; -import {routes, Api, getClient, ServerApi, ApiError} from "@lodestar/api"; +import {routes, getClient, ApiClient} from "@lodestar/api"; import {chainConfig as chainConfigDef} from "@lodestar/config/default"; import {createBeaconConfig, ChainConfig} from "@lodestar/config"; import {Lightclient, LightclientEvent} from "../../src/index.js"; @@ -61,7 +61,7 @@ describe("sync", () => { lightclient: lightclientServerApi, events: eventsServerApi, proof: proofServerApi, - } as Partial<{[K in keyof Api]: ServerApi}> as {[K in keyof Api]: ServerApi}); + }); // Populate initial snapshot const {snapshot, checkpointRoot} = computeLightClientSnapshot(initialPeriod); @@ -177,18 +177,14 @@ describe("sync", () => { // TODO: Re-incorporate for REST-only light-client async function getHeadStateProof( lightclient: Lightclient, - api: Api, + api: ApiClient, paths: JsonPath[] -): Promise<{proof: TreeOffsetProof; header: altair.LightClientHeader}> { +): Promise<{proof: CompactMultiProof; header: altair.LightClientHeader}> { const header = lightclient.getHead(); const stateId = toHexString(header.beacon.stateRoot); const gindices = paths.map((path) => ssz.bellatrix.BeaconState.getPathInfo(path).gindex); const descriptor = computeDescriptor(gindices); - const res = await api.proof.getStateProof(stateId, descriptor); - ApiError.assert(res); + const proof = (await api.proof.getStateProof({stateId, descriptor})).value(); - return { - proof: res.response.data as TreeOffsetProof, - header, - }; + return {proof, header}; } diff --git a/packages/light-client/test/utils/getGenesisData.ts b/packages/light-client/test/utils/getGenesisData.ts index 0a5ce6e9f0e0..8e435eb9d218 100644 --- a/packages/light-client/test/utils/getGenesisData.ts +++ b/packages/light-client/test/utils/getGenesisData.ts @@ -1,4 +1,4 @@ -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {NetworkName} from "@lodestar/config/networks.js"; @@ -16,11 +16,10 @@ async function getGenesisData(): Promise { for (const network of networksInInfura) { const baseUrl = getInfuraBeaconUrl(network); const api = getClient({baseUrl}, {config}); - const res = await api.beacon.getGenesis(); - ApiError.assert(res); + const {genesisTime, genesisValidatorsRoot} = (await api.beacon.getGenesis()).value(); console.log(network, { - genesisTime: Number(res.response.data.genesisTime), - genesisValidatorsRoot: "0x" + Buffer.from(res.response.data.genesisValidatorsRoot).toString("hex"), + genesisTime, + genesisValidatorsRoot: "0x" + Buffer.from(genesisValidatorsRoot).toString("hex"), }); } } diff --git a/packages/light-client/test/utils/server.ts b/packages/light-client/test/utils/server.ts index 5fe1dd623ccb..6cc55e03cee4 100644 --- a/packages/light-client/test/utils/server.ts +++ b/packages/light-client/test/utils/server.ts @@ -1,8 +1,9 @@ import {parse as parseQueryString} from "qs"; import {FastifyInstance, fastify} from "fastify"; import {fastifyCors} from "@fastify/cors"; -import {Api, ServerApi} from "@lodestar/api"; -import {registerRoutes} from "@lodestar/api/beacon/server"; +import {Endpoints} from "@lodestar/api"; +import {ApplicationMethods, addSszContentTypeParser} from "@lodestar/api/server"; +import {BeaconApiMethods, registerRoutes} from "@lodestar/api/beacon/server"; import {ChainForkConfig} from "@lodestar/config"; export type ServerOpts = { @@ -10,10 +11,12 @@ export type ServerOpts = { host: string; }; +type LightClientEndpoints = Pick; + export async function startServer( opts: ServerOpts, config: ChainForkConfig, - api: {[K in keyof Api]: ServerApi} + methods: {[K in keyof LightClientEndpoints]: ApplicationMethods} ): Promise { const server = fastify({ logger: false, @@ -21,7 +24,9 @@ export async function startServer( querystringParser: (str) => parseQueryString(str, {comma: true, parseArrays: false}), }); - registerRoutes(server, config, api, ["lightclient", "proof", "events"]); + addSszContentTypeParser(server); + + registerRoutes(server, config, methods as BeaconApiMethods, ["lightclient", "proof", "events"]); void server.register(fastifyCors, {origin: "*"}); diff --git a/packages/prover/README.md b/packages/prover/README.md index b377448a1726..b98340698bbe 100644 --- a/packages/prover/README.md +++ b/packages/prover/README.md @@ -1,7 +1,7 @@ # Lodestar Eth Consensus Lightclient Prover [![Discord](https://img.shields.io/discord/593655374469660673.svg?label=Discord&logo=discord)](https://discord.gg/aMxzVcr) -[![ETH Beacon APIs Spec v2.1.0](https://img.shields.io/badge/ETH%20beacon--APIs-2.1.0-blue)](https://github.com/ethereum/beacon-APIs/releases/tag/v2.1.0) +[![ETH Beacon APIs Spec v2.5.0](https://img.shields.io/badge/ETH%20beacon--APIs-2.5.0-blue)](https://github.com/ethereum/beacon-APIs/releases/tag/v2.5.0) ![ES Version](https://img.shields.io/badge/ES-2021-yellow) ![Node Version](https://img.shields.io/badge/node-22.x-green) diff --git a/packages/prover/src/proof_provider/payload_store.ts b/packages/prover/src/proof_provider/payload_store.ts index 183a56274e5c..30d5b2126296 100644 --- a/packages/prover/src/proof_provider/payload_store.ts +++ b/packages/prover/src/proof_provider/payload_store.ts @@ -1,4 +1,4 @@ -import {Api} from "@lodestar/api"; +import {ApiClient} from "@lodestar/api"; import {allForks, capella} from "@lodestar/types"; import {Logger} from "@lodestar/utils"; import {MAX_PAYLOAD_HISTORY} from "../constants.js"; @@ -32,7 +32,7 @@ export class PayloadStore { private latestBlockRoot: BlockELRoot | null = null; - constructor(private opts: {api: Api; logger: Logger}) {} + constructor(private opts: {api: ApiClient; logger: Logger}) {} get finalized(): allForks.ExecutionPayload | undefined { const maxBlockNumberForFinalized = this.finalizedRoots.max; diff --git a/packages/prover/src/proof_provider/proof_provider.ts b/packages/prover/src/proof_provider/proof_provider.ts index eaf46c13044b..4cced315da04 100644 --- a/packages/prover/src/proof_provider/proof_provider.ts +++ b/packages/prover/src/proof_provider/proof_provider.ts @@ -1,4 +1,4 @@ -import {Api, getClient} from "@lodestar/api/beacon"; +import {ApiClient, getClient} from "@lodestar/api/beacon"; import {ChainForkConfig, createChainForkConfig} from "@lodestar/config"; import {NetworkName, networksChainConfig} from "@lodestar/config/networks"; import {Lightclient, LightclientEvent, RunStatusCode} from "@lodestar/light-client"; @@ -20,7 +20,7 @@ import {PayloadStore} from "./payload_store.js"; type RootProviderOptions = Omit & { transport: LightClientRestTransport; - api: Api; + api: ApiClient; config: ChainForkConfig; }; @@ -32,7 +32,7 @@ export class ProofProvider { readonly config: ChainForkConfig; readonly network: NetworkName; - readonly api: Api; + readonly api: ApiClient; lightClient?: Lightclient; diff --git a/packages/prover/src/utils/consensus.ts b/packages/prover/src/utils/consensus.ts index a07409458fb6..c34558e96e6d 100644 --- a/packages/prover/src/utils/consensus.ts +++ b/packages/prover/src/utils/consensus.ts @@ -1,28 +1,27 @@ -import {Api} from "@lodestar/api/beacon"; +import {ApiClient} from "@lodestar/api/beacon"; import {allForks, Bytes32, capella} from "@lodestar/types"; import {GenesisData, Lightclient} from "@lodestar/light-client"; -import {ApiError} from "@lodestar/api"; import {Logger} from "@lodestar/utils"; import {MAX_PAYLOAD_HISTORY} from "../constants.js"; import {hexToBuffer} from "./conversion.js"; -export async function fetchBlock(api: Api, slot: number): Promise { - const res = await api.beacon.getBlockV2(slot); +export async function fetchBlock(api: ApiClient, slot: number): Promise { + const res = await api.beacon.getBlockV2({blockId: slot}); - if (res.ok) return res.response.data as capella.SignedBeaconBlock; + if (res.ok) return res.value() as capella.SignedBeaconBlock; return; } export async function fetchNearestBlock( - api: Api, + api: ApiClient, slot: number, direction: "up" | "down" = "down" ): Promise { - const res = await api.beacon.getBlockV2(slot); + const res = await api.beacon.getBlockV2({blockId: slot}); - if (res.ok) return res.response.data as capella.SignedBeaconBlock; + if (res.ok) return res.value() as capella.SignedBeaconBlock; - if (!res.ok && res.error.code === 404) { + if (!res.ok && res.status === 404) { return fetchNearestBlock(api, direction === "down" ? slot - 1 : slot + 1); } @@ -46,7 +45,7 @@ export async function getExecutionPayloads({ endSlot, logger, }: { - api: Api; + api: ApiClient; startSlot: number; endSlot: number; logger: Logger; @@ -80,7 +79,7 @@ export async function getExecutionPayloads({ } export async function getExecutionPayloadForBlockNumber( - api: Api, + api: ApiClient, startSlot: number, blockNumber: number ): Promise> { @@ -98,17 +97,16 @@ export async function getExecutionPayloadForBlockNumber( return payloads; } -export async function getGenesisData(api: Pick): Promise { - const res = await api.beacon.getGenesis(); - ApiError.assert(res); +export async function getGenesisData(api: Pick): Promise { + const {genesisTime, genesisValidatorsRoot} = (await api.beacon.getGenesis()).value(); return { - genesisTime: Number(res.response.data.genesisTime), - genesisValidatorsRoot: res.response.data.genesisValidatorsRoot, + genesisTime, + genesisValidatorsRoot, }; } -export async function getSyncCheckpoint(api: Pick, checkpoint?: string): Promise { +export async function getSyncCheckpoint(api: Pick, checkpoint?: string): Promise { let syncCheckpoint: Bytes32 | undefined = checkpoint ? hexToBuffer(checkpoint) : undefined; if (syncCheckpoint && syncCheckpoint.byteLength !== 32) { @@ -116,9 +114,8 @@ export async function getSyncCheckpoint(api: Pick, checkpoint?: s } if (!syncCheckpoint) { - const res = await api.beacon.getStateFinalityCheckpoints("head"); - ApiError.assert(res); - syncCheckpoint = res.response.data.finalized.root; + const res = await api.beacon.getStateFinalityCheckpoints({stateId: "head"}); + syncCheckpoint = res.value().finalized.root; } return syncCheckpoint; diff --git a/packages/prover/test/unit/proof_provider/payload_store.test.ts b/packages/prover/test/unit/proof_provider/payload_store.test.ts index ad3e0fabad06..81b8ffe3c16c 100644 --- a/packages/prover/test/unit/proof_provider/payload_store.test.ts +++ b/packages/prover/test/unit/proof_provider/payload_store.test.ts @@ -1,6 +1,6 @@ import {describe, it, expect, beforeEach, vi, MockedObject} from "vitest"; import {when} from "vitest-when"; -import {Api, HttpStatusCode, routes} from "@lodestar/api"; +import {ApiClient, ApiResponse, HttpStatusCode, routes} from "@lodestar/api"; import {hash} from "@lodestar/utils"; import {Logger} from "@lodestar/logger"; import {allForks, capella} from "@lodestar/types"; @@ -45,24 +45,21 @@ const buildBlockResponse = ({ }: { slot: number; blockNumber: number; -}): routes.beacon.block.BlockV2Response<"json"> => ({ - ok: true, - status: HttpStatusCode.OK, - response: { - version: ForkName.altair, - executionOptimistic: true, - finalized: false, - data: buildBlock({slot, blockNumber}), - }, -}); +}): ApiResponse => { + const response = new Response(null, {status: HttpStatusCode.OK}); + const apiResponse = new ApiResponse({} as any, null, response); + apiResponse.value = () => buildBlock({slot, blockNumber}); + apiResponse.meta = () => ({version: ForkName.altair, executionOptimistic: true, finalized: false}); + return apiResponse; +}; describe("proof_provider/payload_store", function () { - let api: Api & {beacon: MockedObject}; + let api: ApiClient & {beacon: MockedObject}; let logger: Logger; let store: PayloadStore; beforeEach(() => { - api = {beacon: {getBlockV2: vi.fn()}} as unknown as Api & {beacon: MockedObject}; + api = {beacon: {getBlockV2: vi.fn()}} as unknown as ApiClient & {beacon: MockedObject}; logger = console as unknown as Logger; store = new PayloadStore({api, logger}); }); @@ -194,11 +191,11 @@ describe("proof_provider/payload_store", function () { const unavailablePayload = buildPayload({blockNumber: unavailableBlockNumber}); when(api.beacon.getBlockV2) - .calledWith(blockNumber) + .calledWith({blockId: blockNumber}) .thenResolve(buildBlockResponse({blockNumber, slot: blockNumber})); when(api.beacon.getBlockV2) - .calledWith(unavailableBlockNumber) + .calledWith({blockId: unavailableBlockNumber}) .thenResolve(buildBlockResponse({blockNumber: unavailableBlockNumber, slot: unavailableBlockNumber})); store.set(availablePayload, slotNumber, true); @@ -206,8 +203,8 @@ describe("proof_provider/payload_store", function () { const result = await store.get(unavailablePayload.blockNumber); expect(api.beacon.getBlockV2).toHaveBeenCalledTimes(2); - expect(api.beacon.getBlockV2).toHaveBeenCalledWith(blockNumber); - expect(api.beacon.getBlockV2).toHaveBeenCalledWith(unavailableBlockNumber); + expect(api.beacon.getBlockV2).toHaveBeenCalledWith({blockId: blockNumber}); + expect(api.beacon.getBlockV2).toHaveBeenCalledWith({blockId: unavailableBlockNumber}); expect(result).toEqual(unavailablePayload); }); }); @@ -243,14 +240,13 @@ describe("proof_provider/payload_store", function () { const slot = 20; const header = buildLCHeader({slot, blockNumber}); const blockResponse = buildBlockResponse({blockNumber, slot}); - const executionPayload = (blockResponse.response?.data as capella.SignedBeaconBlock).message.body - .executionPayload; + const executionPayload = (blockResponse.value() as capella.SignedBeaconBlock).message.body.executionPayload; api.beacon.getBlockV2.mockResolvedValue(blockResponse); await store.processLCHeader(header, true); expect(api.beacon.getBlockV2).toHaveBeenCalledOnce(); - expect(api.beacon.getBlockV2).toHaveBeenCalledWith(20); + expect(api.beacon.getBlockV2).toHaveBeenCalledWith({blockId: 20}); expect(store.finalized).toEqual(executionPayload); }); @@ -259,8 +255,7 @@ describe("proof_provider/payload_store", function () { const slot = 20; const header = buildLCHeader({slot, blockNumber}); const blockResponse = buildBlockResponse({blockNumber, slot}); - const executionPayload = (blockResponse.response?.data as capella.SignedBeaconBlock).message.body - .executionPayload; + const executionPayload = (blockResponse.value() as capella.SignedBeaconBlock).message.body.executionPayload; api.beacon.getBlockV2.mockResolvedValue(blockResponse); expect(store.finalized).toBeUndefined(); // First process as unfinalized @@ -284,7 +279,7 @@ describe("proof_provider/payload_store", function () { await store.processLCHeader(header); expect(api.beacon.getBlockV2).toHaveBeenCalledOnce(); - expect(api.beacon.getBlockV2).toHaveBeenCalledWith(20); + expect(api.beacon.getBlockV2).toHaveBeenCalledWith({blockId: 20}); }); it("should not fetch existing payload for lightclient header", async () => { @@ -300,7 +295,7 @@ describe("proof_provider/payload_store", function () { // The network fetch should be done once expect(api.beacon.getBlockV2).toHaveBeenCalledOnce(); - expect(api.beacon.getBlockV2).toHaveBeenCalledWith(20); + expect(api.beacon.getBlockV2).toHaveBeenCalledWith({blockId: 20}); }); it("should prune the existing payloads", async () => { @@ -383,7 +378,7 @@ describe("proof_provider/payload_store", function () { for (let i = 1; i <= numberOfPayloads; i++) { when(api.beacon.getBlockV2) - .calledWith(i) + .calledWith({blockId: i}) .thenResolve(buildBlockResponse({blockNumber: 500 + i, slot: i})); await store.processLCHeader(buildLCHeader({blockNumber: 500 + i, slot: i}), false); diff --git a/packages/reqresp/README.md b/packages/reqresp/README.md index dbfbf304c541..65d0c43b47b0 100644 --- a/packages/reqresp/README.md +++ b/packages/reqresp/README.md @@ -1,7 +1,7 @@ # Lodestar Eth Consensus Req/Resp Protocol [![Discord](https://img.shields.io/discord/593655374469660673.svg?label=Discord&logo=discord)](https://discord.gg/aMxzVcr) -[![ETH Beacon APIs Spec v2.1.0](https://img.shields.io/badge/ETH%20beacon--APIs-2.1.0-blue)](https://github.com/ethereum/beacon-APIs/releases/tag/v2.1.0) +[![ETH Beacon APIs Spec v2.5.0](https://img.shields.io/badge/ETH%20beacon--APIs-2.5.0-blue)](https://github.com/ethereum/beacon-APIs/releases/tag/v2.5.0) ![ES Version](https://img.shields.io/badge/ES-2021-yellow) ![Node Version](https://img.shields.io/badge/node-22.x-green) diff --git a/packages/state-transition/test/perf/analyzeBlocks.ts b/packages/state-transition/test/perf/analyzeBlocks.ts index f9e26b4f5238..71fd5952799a 100644 --- a/packages/state-transition/test/perf/analyzeBlocks.ts +++ b/packages/state-transition/test/perf/analyzeBlocks.ts @@ -1,6 +1,5 @@ -import {getClient, ApiError} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; -import {allForks} from "@lodestar/types"; import {getInfuraBeaconUrl} from "../utils/infura.js"; // Analyze how Ethereum Consensus blocks are in a target network to prepare accurate performance states and blocks @@ -20,9 +19,7 @@ const network = "mainnet"; const client = getClient({baseUrl: getInfuraBeaconUrl(network)}, {config}); async function run(): Promise { - const res = await client.beacon.getBlockHeader("head"); - ApiError.assert(res); - const headBlock = res.response.data; + const headBlock = (await client.beacon.getBlockHeader({blockId: "head"})).value(); // Count operations let blocks = 0; @@ -40,9 +37,9 @@ async function run(): Promise { const batchSize = 32; for (let slot = startSlot; slot > 0; slot -= batchSize) { - const blockPromises: ReturnType[] = []; + const blockPromises: ReturnType[] = []; for (let s = slot - batchSize; s < slot; s++) { - blockPromises.push(client.beacon.getBlock(s)); + blockPromises.push(client.beacon.getBlockV2({blockId: s})); } const results = await Promise.allSettled(blockPromises); @@ -51,9 +48,8 @@ async function run(): Promise { // Missed block continue; } - ApiError.assert(result.value); - const block = (result.value.response as {data: allForks.SignedBeaconBlock}).data; + const block = result.value.value(); blocks++; attestations += block.message.body.attestations.length; diff --git a/packages/state-transition/test/perf/analyzeEpochs.ts b/packages/state-transition/test/perf/analyzeEpochs.ts index df072a75902b..4b793fe95e6e 100644 --- a/packages/state-transition/test/perf/analyzeEpochs.ts +++ b/packages/state-transition/test/perf/analyzeEpochs.ts @@ -1,5 +1,5 @@ import fs from "node:fs"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {NetworkName} from "@lodestar/config/networks.js"; import {phase0, ssz} from "@lodestar/types"; @@ -77,22 +77,18 @@ async function analyzeEpochs(network: NetworkName, fromEpoch?: number): Promise< const baseUrl = getInfuraBeaconUrl(network); // Long timeout to download states - const client = getClient({baseUrl, timeoutMs: 5 * 60 * 1000}, {config}); + const client = getClient({baseUrl, globalInit: {timeoutMs: 5 * 60 * 1000}}, {config}); // Start at epoch 1 since 0 will go and fetch state at slot -1 const maxEpoch = fromEpoch ?? Math.max(1, ...currCsv.map((row) => row.epoch)); - const res = await client.beacon.getBlockHeader("head"); - ApiError.assert(res); - const header = res.response.data; - const currentEpoch = computeEpochAtSlot(header.header.message.slot); + const {header} = (await client.beacon.getBlockHeader({blockId: "head"})).value(); + const currentEpoch = computeEpochAtSlot(header.message.slot); for (let epoch = maxEpoch; epoch < currentEpoch; epoch++) { const stateSlot = computeStartSlotAtEpoch(epoch) - 1; - const res = await client.debug.getState(String(stateSlot)); - ApiError.assert(res); - const state = res.response.data; + const state = (await client.debug.getStateV2({stateId: stateSlot})).value(); const preEpoch = computeEpochAtSlot(state.slot); const nextEpochSlot = computeStartSlotAtEpoch(preEpoch + 1); diff --git a/packages/state-transition/test/utils/testFileCache.ts b/packages/state-transition/test/utils/testFileCache.ts index e752f3c36e68..6e554d888d18 100644 --- a/packages/state-transition/test/utils/testFileCache.ts +++ b/packages/state-transition/test/utils/testFileCache.ts @@ -1,11 +1,11 @@ import fs from "node:fs"; import path from "node:path"; import got from "got"; -import {ApiError, getClient} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {NetworkName, networksChainConfig} from "@lodestar/config/networks"; import {createChainForkConfig, ChainForkConfig} from "@lodestar/config"; import {allForks} from "@lodestar/types"; -import {CachedBeaconStateAllForks, computeEpochAtSlot} from "../../src/index.js"; +import {CachedBeaconStateAllForks} from "../../src/index.js"; import {testCachePath} from "../cache.js"; import {createCachedBeaconStateTest} from "../utils/state.js"; import {getInfuraBeaconUrl} from "./infura.js"; @@ -45,16 +45,13 @@ export async function getNetworkCachedState( const stateSsz = await tryEach([ () => downloadTestFile(fileId), () => { - const client = getClient({baseUrl: getInfuraBeaconUrl(network), timeoutMs: timeout ?? 300_000}, {config}); - return computeEpochAtSlot(slot) < config.ALTAIR_FORK_EPOCH - ? client.debug.getState(String(slot), "ssz").then((r) => { - ApiError.assert(r); - return r.response; - }) - : client.debug.getStateV2(String(slot), "ssz").then((r) => { - ApiError.assert(r); - return r.response; - }); + const client = getClient( + {baseUrl: getInfuraBeaconUrl(network), globalInit: {timeoutMs: timeout ?? 300_000}}, + {config} + ); + return client.debug.getStateV2({stateId: slot}).then((r) => { + return r.ssz(); + }); }, ]); @@ -83,14 +80,12 @@ export async function getNetworkCachedBlock( const blockSsz = await tryEach([ () => downloadTestFile(fileId), async () => { - const client = getClient({baseUrl: getInfuraBeaconUrl(network), timeoutMs: timeout ?? 300_000}, {config}); - - const res = - computeEpochAtSlot(slot) < config.ALTAIR_FORK_EPOCH - ? await client.beacon.getBlock(String(slot)) - : await client.beacon.getBlockV2(String(slot)); - ApiError.assert(res); - return config.getForkTypes(slot).SignedBeaconBlock.serialize(res.response.data); + const client = getClient( + {baseUrl: getInfuraBeaconUrl(network), globalInit: {timeoutMs: timeout ?? 300_000}}, + {config} + ); + + return (await client.beacon.getBlockV2({blockId: slot})).ssz(); }, ]); diff --git a/packages/types/src/utils/stringType.ts b/packages/types/src/utils/stringType.ts index b3ef4f2f1f18..89fa93ec0317 100644 --- a/packages/types/src/utils/stringType.ts +++ b/packages/types/src/utils/stringType.ts @@ -44,6 +44,9 @@ export class StringType extends BasicType { // JSON fromJson(json: unknown): T { + if (typeof json !== "string") { + throw Error(`JSON invalid type ${typeof json} expected string`); + } return json as T; } diff --git a/packages/validator/src/genesis.ts b/packages/validator/src/genesis.ts index 2442d4882ff3..3156acee689f 100644 --- a/packages/validator/src/genesis.ts +++ b/packages/validator/src/genesis.ts @@ -1,17 +1,15 @@ import {Genesis} from "@lodestar/types/phase0"; import {Logger, sleep} from "@lodestar/utils"; -import {Api, ApiError} from "@lodestar/api"; +import {ApiClient} from "@lodestar/api"; /** The time between polls when waiting for genesis */ const WAITING_FOR_GENESIS_POLL_MS = 12 * 1000; -export async function waitForGenesis(api: Api, logger: Logger, signal?: AbortSignal): Promise { +export async function waitForGenesis(api: ApiClient, logger: Logger, signal?: AbortSignal): Promise { // eslint-disable-next-line no-constant-condition while (true) { try { - const res = await api.beacon.getGenesis(); - ApiError.assert(res, "Can not fetch genesis data from beacon node"); - return res.response.data; + return (await api.beacon.getGenesis()).value(); } catch (e) { // TODO: Search for a 404 error which indicates that genesis has not yet occurred. // Note: Lodestar API does not become online after genesis is found diff --git a/packages/validator/src/services/attestation.ts b/packages/validator/src/services/attestation.ts index 73162b67f1af..57a8a7621a97 100644 --- a/packages/validator/src/services/attestation.ts +++ b/packages/validator/src/services/attestation.ts @@ -2,7 +2,7 @@ import {toHexString} from "@chainsafe/ssz"; import {BLSSignature, phase0, Slot, ssz} from "@lodestar/types"; import {computeEpochAtSlot, isAggregatorFromCommitteeLength} from "@lodestar/state-transition"; import {sleep} from "@lodestar/utils"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {IClock, LoggerVc} from "../util/index.js"; import {PubkeyHex} from "../types.js"; import {Metrics} from "../metrics.js"; @@ -35,7 +35,7 @@ export class AttestationService { constructor( private readonly logger: LoggerVc, - private readonly api: Api, + private readonly api: ApiClient, private readonly clock: IClock, private readonly validatorStore: ValidatorStore, private readonly emitter: ValidatorEventEmitter, @@ -167,9 +167,7 @@ export class AttestationService { */ private async produceAttestation(committeeIndex: number, slot: Slot): Promise { // Produce one attestation data per slot and committeeIndex - const res = await this.api.validator.produceAttestationData(committeeIndex, slot); - ApiError.assert(res, "Error producing attestation"); - return res.response.data; + return (await this.api.validator.produceAttestationData({committeeIndex, slot})).value(); } /** @@ -223,7 +221,7 @@ export class AttestationService { ...(this.opts?.disableAttestationGrouping && {index: attestationNoCommittee.index}), }; try { - ApiError.assert(await this.api.beacon.submitPoolAttestations(signedAttestations)); + (await this.api.beacon.submitPoolAttestations({signedAttestations})).assertOk(); this.logger.info("Published attestations", {...logCtx, count: signedAttestations.length}); this.metrics?.publishedAttestations.inc(signedAttestations.length); } catch (e) { @@ -255,13 +253,12 @@ export class AttestationService { } this.logger.verbose("Aggregating attestations", logCtx); - const res = await this.api.validator.getAggregatedAttestation( - ssz.phase0.AttestationData.hashTreeRoot(attestation), - attestation.slot - ); - ApiError.assert(res, "Error producing aggregateAndProofs"); - const aggregate = res.response; - this.metrics?.numParticipantsInAggregate.observe(aggregate.data.aggregationBits.getTrueBitIndexes().length); + const res = await this.api.validator.getAggregatedAttestation({ + attestationDataRoot: ssz.phase0.AttestationData.hashTreeRoot(attestation), + slot: attestation.slot, + }); + const aggregate = res.value(); + this.metrics?.numParticipantsInAggregate.observe(aggregate.aggregationBits.getTrueBitIndexes().length); const signedAggregateAndProofs: phase0.SignedAggregateAndProof[] = []; @@ -272,7 +269,7 @@ export class AttestationService { // Produce signed aggregates only for validators that are subscribed aggregators. if (selectionProof !== null) { signedAggregateAndProofs.push( - await this.validatorStore.signAggregateAndProof(duty, selectionProof, aggregate.data) + await this.validatorStore.signAggregateAndProof(duty, selectionProof, aggregate) ); this.logger.debug("Signed aggregateAndProofs", logCtxValidator); } @@ -286,8 +283,7 @@ export class AttestationService { if (signedAggregateAndProofs.length > 0) { try { - const res = await this.api.validator.publishAggregateAndProofs(signedAggregateAndProofs); - ApiError.assert(res); + (await this.api.validator.publishAggregateAndProofs({signedAggregateAndProofs})).assertOk(); this.logger.info("Published aggregateAndProofs", {...logCtx, count: signedAggregateAndProofs.length}); this.metrics?.publishedAggregates.inc(signedAggregateAndProofs.length); } catch (e) { @@ -322,7 +318,7 @@ export class AttestationService { this.logger.debug("Submitting partial beacon committee selection proofs", {slot, count: partialSelections.length}); const res = await Promise.race([ - this.api.validator.submitBeaconCommitteeSelections(partialSelections), + this.api.validator.submitBeaconCommitteeSelections({selections: partialSelections}), // Exit attestation aggregation flow if there is no response after 1/3 of slot as // beacon node would likely not have enough time to prepare an aggregate attestation. // Note that the aggregations flow is not explicitly exited but rather will be skipped @@ -334,9 +330,8 @@ export class AttestationService { if (!res) { throw new Error("Failed to receive combined selection proofs before 1/3 of slot"); } - ApiError.assert(res, "Error receiving combined selection proofs"); - const combinedSelections = res.response.data; + const combinedSelections = res.value(); this.logger.debug("Received combined beacon committee selection proofs", {slot, count: combinedSelections.length}); const beaconCommitteeSubscriptions: routes.validator.BeaconCommitteeSubscription[] = []; @@ -373,10 +368,7 @@ export class AttestationService { // If there are any subscriptions with aggregators, push them out to the beacon node. if (beaconCommitteeSubscriptions.length > 0) { - ApiError.assert( - await this.api.validator.prepareBeaconCommitteeSubnet(beaconCommitteeSubscriptions), - "Failed to resubscribe to beacon committee subnets" - ); + (await this.api.validator.prepareBeaconCommitteeSubnet({subscriptions: beaconCommitteeSubscriptions})).assertOk(); this.logger.debug("Resubscribed validators as aggregators on beacon committee subnets", { slot, count: beaconCommitteeSubscriptions.length, diff --git a/packages/validator/src/services/attestationDuties.ts b/packages/validator/src/services/attestationDuties.ts index 8fa127c25e8b..1f278aebbd89 100644 --- a/packages/validator/src/services/attestationDuties.ts +++ b/packages/validator/src/services/attestationDuties.ts @@ -3,7 +3,7 @@ import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {sleep} from "@lodestar/utils"; import {computeEpochAtSlot, isAggregatorFromCommitteeLength} from "@lodestar/state-transition"; import {BLSSignature, Epoch, Slot, ValidatorIndex, RootHex} from "@lodestar/types"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {batchItems, IClock, LoggerVc} from "../util/index.js"; import {PubkeyHex} from "../types.js"; import {Metrics} from "../metrics.js"; @@ -48,7 +48,7 @@ export class AttestationDutiesService { constructor( private readonly logger: LoggerVc, - private readonly api: Api, + private readonly api: ApiClient, private clock: IClock, private readonly validatorStore: ValidatorStore, chainHeadTracker: ChainHeaderTracker, @@ -213,10 +213,12 @@ export class AttestationDutiesService { // If there are any subscriptions, push them out to the beacon node. if (beaconCommitteeSubscriptions.length > 0) { const subscriptionsBatches = batchItems(beaconCommitteeSubscriptions, {batchSize: SUBSCRIPTIONS_PER_REQUEST}); - const responses = await Promise.all(subscriptionsBatches.map(this.api.validator.prepareBeaconCommitteeSubnet)); + const responses = await Promise.all( + subscriptionsBatches.map((subscriptions) => this.api.validator.prepareBeaconCommitteeSubnet({subscriptions})) + ); for (const res of responses) { - ApiError.assert(res, "Failed to subscribe to beacon committee subnets"); + res.assertOk(); } } } @@ -230,11 +232,10 @@ export class AttestationDutiesService { return; } - const res = await this.api.validator.getAttesterDuties(epoch, indexArr); - ApiError.assert(res, "Failed to obtain attester duty"); - const attesterDuties = res.response; - const {dependentRoot} = attesterDuties; - const relevantDuties = attesterDuties.data.filter((duty) => { + const res = await this.api.validator.getAttesterDuties({epoch, indices: indexArr}); + const attesterDuties = res.value(); + const {dependentRoot} = res.meta(); + const relevantDuties = attesterDuties.filter((duty) => { const pubkeyHex = toHexString(duty.pubkey); return this.validatorStore.hasVotingPubkey(pubkeyHex) && this.validatorStore.isDoppelgangerSafe(pubkeyHex); }); diff --git a/packages/validator/src/services/block.ts b/packages/validator/src/services/block.ts index faec8d9e04ee..ab855e264447 100644 --- a/packages/validator/src/services/block.ts +++ b/packages/validator/src/services/block.ts @@ -10,9 +10,9 @@ import { isBlockContents, } from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; -import {ForkPreBlobs, ForkBlobs, ForkSeq, ForkExecution} from "@lodestar/params"; +import {ForkPreBlobs, ForkBlobs, ForkSeq, ForkExecution, ForkName} from "@lodestar/params"; import {extendError, prettyBytes, prettyWeiToEth} from "@lodestar/utils"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {IClock, LoggerVc} from "../util/index.js"; import {PubkeyHex} from "../types.js"; import {Metrics} from "../metrics.js"; @@ -64,7 +64,7 @@ export class BlockProposingService { constructor( private readonly config: ChainForkConfig, private readonly logger: LoggerVc, - private readonly api: Api, + private readonly api: ApiClient, private readonly clock: IClock, private readonly validatorStore: ValidatorStore, private readonly metrics: Metrics | null, @@ -138,7 +138,6 @@ export class BlockProposingService { const produceOpts = { feeRecipient, strictFeeRecipientCheck, - builderBoostFactor, blindedLocal, }; const blockContents = await produceBlockFn( @@ -146,6 +145,7 @@ export class BlockProposingService { slot, randaoReveal, graffiti, + builderBoostFactor, produceOpts, builderSelection ).catch((e: Error) => { @@ -184,12 +184,12 @@ export class BlockProposingService { "Ignoring contents while publishing blinded block - publishing beacon should assemble it from its local cache or builder" ); } - ApiError.assert(await this.api.beacon.publishBlindedBlockV2(signedBlock, opts)); + (await this.api.beacon.publishBlindedBlockV2({signedBlindedBlock: signedBlock, ...opts})).assertOk(); } else { if (contents === null) { - ApiError.assert(await this.api.beacon.publishBlockV2(signedBlock, opts)); + (await this.api.beacon.publishBlockV2({signedBlockOrContents: signedBlock, ...opts})).assertOk(); } else { - ApiError.assert(await this.api.beacon.publishBlockV2({...contents, signedBlock}, opts)); + (await this.api.beacon.publishBlockV2({signedBlockOrContents: {...contents, signedBlock}, ...opts})).assertOk(); } } }; @@ -199,32 +199,36 @@ export class BlockProposingService { slot: Slot, randaoReveal: BLSSignature, graffiti: string, - {feeRecipient, strictFeeRecipientCheck, builderBoostFactor, blindedLocal}: routes.validator.ExtraProduceBlockOps, + builderBoostFactor: bigint, + {feeRecipient, strictFeeRecipientCheck, blindedLocal}: routes.validator.ExtraProduceBlockOpts, builderSelection: routes.validator.BuilderSelection ): Promise => { - const res = await this.api.validator.produceBlockV3(slot, randaoReveal, graffiti, false, { + const res = await this.api.validator.produceBlockV3({ + slot, + randaoReveal, + graffiti, + skipRandaoVerification: false, feeRecipient, builderSelection, strictFeeRecipientCheck, blindedLocal, builderBoostFactor, }); - ApiError.assert(res, "Failed to produce block: validator.produceBlockV3"); - const {response} = res; + const meta = res.meta(); const debugLogCtx = { - executionPayloadSource: response.executionPayloadSource, - executionPayloadBlinded: response.executionPayloadBlinded, - executionPayloadValue: prettyWeiToEth(response.executionPayloadValue), - consensusBlockValue: prettyWeiToEth(response.consensusBlockValue), - totalBlockValue: prettyWeiToEth(response.executionPayloadValue + response.consensusBlockValue), + executionPayloadSource: meta.executionPayloadSource, + executionPayloadBlinded: meta.executionPayloadBlinded, + executionPayloadValue: prettyWeiToEth(meta.executionPayloadValue), + consensusBlockValue: prettyWeiToEth(meta.consensusBlockValue), + totalBlockValue: prettyWeiToEth(meta.executionPayloadValue + meta.consensusBlockValue), // TODO PR: should be used in api call instead of adding in log strictFeeRecipientCheck, builderSelection, api: "produceBlockV3", }; - return parseProduceBlockResponse(response, debugLogCtx, builderSelection); + return parseProduceBlockResponse({data: res.value(), ...meta}, debugLogCtx, builderSelection); }; /** a wrapper function used for backward compatibility with the clients who don't have v3 implemented yet */ @@ -233,7 +237,8 @@ export class BlockProposingService { slot: Slot, randaoReveal: BLSSignature, graffiti: string, - _opts: routes.validator.ExtraProduceBlockOps, + _builderBoostFactor: bigint, + _opts: routes.validator.ExtraProduceBlockOpts, builderSelection: routes.validator.BuilderSelection ): Promise => { // other clients have always implemented builder vs execution race in produce blinded block @@ -243,25 +248,23 @@ export class BlockProposingService { if (ForkSeq[fork] < ForkSeq.bellatrix || builderSelection === routes.validator.BuilderSelection.ExecutionOnly) { Object.assign(debugLogCtx, {api: "produceBlockV2"}); - const res = await this.api.validator.produceBlockV2(slot, randaoReveal, graffiti); - ApiError.assert(res, "Failed to produce block: validator.produceBlockV2"); - const {response} = res; + const res = await this.api.validator.produceBlockV2({slot, randaoReveal, graffiti}); + const {version} = res.meta(); const executionPayloadSource = ProducedBlockSource.engine; return parseProduceBlockResponse( - {executionPayloadBlinded: false, executionPayloadSource, ...response}, + {data: res.value(), executionPayloadBlinded: false, executionPayloadSource, version}, debugLogCtx, builderSelection ); } else { Object.assign(debugLogCtx, {api: "produceBlindedBlock"}); - const res = await this.api.validator.produceBlindedBlock(slot, randaoReveal, graffiti); - ApiError.assert(res, "Failed to produce block: validator.produceBlindedBlock"); - const {response} = res; + const res = await this.api.validator.produceBlindedBlock({slot, randaoReveal, graffiti}); + const {version} = res.meta(); const executionPayloadSource = ProducedBlockSource.builder; return parseProduceBlockResponse( - {executionPayloadBlinded: true, executionPayloadSource, ...response}, + {data: res.value(), executionPayloadBlinded: true, executionPayloadSource, version}, debugLogCtx, builderSelection ); @@ -270,7 +273,11 @@ export class BlockProposingService { } function parseProduceBlockResponse( - response: routes.validator.ProduceFullOrBlindedBlockOrContentsRes, + response: {data: allForks.FullOrBlindedBeaconBlockOrContents} & { + executionPayloadSource: ProducedBlockSource; + executionPayloadBlinded: boolean; + version: ForkName; + }, debugLogCtx: Record, builderSelection: routes.validator.BuilderSelection ): FullOrBlindedBlockWithContents & DebugLogCtx { diff --git a/packages/validator/src/services/blockDuties.ts b/packages/validator/src/services/blockDuties.ts index 67b6e5834417..3282987f5d9e 100644 --- a/packages/validator/src/services/blockDuties.ts +++ b/packages/validator/src/services/blockDuties.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {BLSPubkey, Epoch, RootHex, Slot} from "@lodestar/types"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {sleep} from "@lodestar/utils"; import {ChainConfig} from "@lodestar/config"; import {IClock, differenceHex, LoggerVc} from "../util/index.js"; @@ -33,7 +33,7 @@ export class BlockDutiesService { constructor( private readonly config: ChainConfig, private readonly logger: LoggerVc, - private readonly api: Api, + private readonly api: ApiClient, private readonly clock: IClock, private readonly validatorStore: ValidatorStore, private readonly metrics: Metrics | null, @@ -183,11 +183,10 @@ export class BlockDutiesService { return; } - const res = await this.api.validator.getProposerDuties(epoch); - ApiError.assert(res, "Error on getProposerDuties"); - const proposerDuties = res.response; - const {dependentRoot} = proposerDuties; - const relevantDuties = proposerDuties.data.filter((duty) => { + const res = await this.api.validator.getProposerDuties({epoch}); + const proposerDuties = res.value(); + const {dependentRoot} = res.meta(); + const relevantDuties = proposerDuties.filter((duty) => { const pubkeyHex = toHexString(duty.pubkey); return this.validatorStore.hasVotingPubkey(pubkeyHex) && this.validatorStore.isDoppelgangerSafe(pubkeyHex); }); diff --git a/packages/validator/src/services/chainHeaderTracker.ts b/packages/validator/src/services/chainHeaderTracker.ts index ebb20670bb24..845743264d0b 100644 --- a/packages/validator/src/services/chainHeaderTracker.ts +++ b/packages/validator/src/services/chainHeaderTracker.ts @@ -1,5 +1,5 @@ import {fromHexString} from "@chainsafe/ssz"; -import {Api, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {Logger} from "@lodestar/utils"; import {Slot, Root, RootHex} from "@lodestar/types"; import {GENESIS_SLOT} from "@lodestar/params"; @@ -26,14 +26,24 @@ export class ChainHeaderTracker { constructor( private readonly logger: Logger, - private readonly api: Api, + private readonly api: ApiClient, private readonly emitter: ValidatorEventEmitter ) {} start(signal: AbortSignal): void { this.logger.verbose("Subscribing to head event"); this.api.events - .eventstream([EventType.head], signal, this.onHeadUpdate) + .eventstream({ + topics: [EventType.head], + signal, + onEvent: this.onHeadUpdate, + onError: (e) => { + this.logger.error("Failed to receive head event", {}, e); + }, + onClose: () => { + this.logger.verbose("Closed stream for head event", {}); + }, + }) .catch((e) => this.logger.error("Failed to subscribe to head event", {}, e)); } diff --git a/packages/validator/src/services/doppelgangerService.ts b/packages/validator/src/services/doppelgangerService.ts index 861db27769e7..5435c5aed37f 100644 --- a/packages/validator/src/services/doppelgangerService.ts +++ b/packages/validator/src/services/doppelgangerService.ts @@ -1,6 +1,6 @@ import {fromHexString} from "@chainsafe/ssz"; import {Epoch, ValidatorIndex} from "@lodestar/types"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {Logger, sleep, truncBytes} from "@lodestar/utils"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {ISlashingProtection} from "../slashingProtection/index.js"; @@ -43,7 +43,7 @@ export class DoppelgangerService { constructor( private readonly logger: Logger, private readonly clock: IClock, - private readonly api: Api, + private readonly api: ApiClient, private readonly indicesService: IndicesService, private readonly slashingProtection: ISlashingProtection, private readonly processShutdownCallback: ProcessShutdownCallback, @@ -170,16 +170,12 @@ export class DoppelgangerService { return {epoch, responses: []}; } - const res = await this.api.validator.getLiveness(epoch, indicesToCheck); + const res = await this.api.validator.getLiveness({epoch, indices: indicesToCheck}); if (!res.ok) { - this.logger.error( - `Error getting liveness data for epoch ${epoch}`, - {}, - new ApiError(res.error.message ?? "", res.error.code, "validator.getLiveness") - ); + this.logger.error(`Error getting liveness data for epoch ${epoch}`, {}, res.error() as Error); return {epoch, responses: []}; } - return {epoch, responses: res.response.data}; + return {epoch, responses: res.value()}; } private detectDoppelganger( diff --git a/packages/validator/src/services/indices.ts b/packages/validator/src/services/indices.ts index 12de1bbb9392..dcc129cc591e 100644 --- a/packages/validator/src/services/indices.ts +++ b/packages/validator/src/services/indices.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; import {ValidatorIndex} from "@lodestar/types"; import {Logger, MapDef} from "@lodestar/utils"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {batchItems} from "../util/index.js"; import {Metrics} from "../metrics.js"; @@ -48,7 +48,7 @@ export class IndicesService { constructor( private readonly logger: Logger, - private readonly api: Api, + private readonly api: ApiClient, private readonly metrics: Metrics | null ) { if (metrics) { @@ -126,32 +126,31 @@ export class IndicesService { } private async fetchValidatorIndices(pubkeysHex: string[]): Promise { - const res = await this.api.beacon.getStateValidators("head", {id: pubkeysHex}); - ApiError.assert(res, "Can not fetch state validators from beacon node"); + const validators = (await this.api.beacon.getStateValidators({stateId: "head", validatorIds: pubkeysHex})).value(); const newIndices = []; const allValidatorStatuses = new MapDef(() => 0); - for (const validatorState of res.response.data) { + for (const validator of validators) { // Group all validators by status - const status = statusToSimpleStatusMapping(validatorState.status); + const status = statusToSimpleStatusMapping(validator.status); allValidatorStatuses.set(status, allValidatorStatuses.getOrDefault(status) + 1); - const pubkeyHex = toHexString(validatorState.validator.pubkey); + const pubkeyHex = toHexString(validator.validator.pubkey); if (!this.pubkey2index.has(pubkeyHex)) { this.logger.info("Validator seen on beacon chain", { - validatorIndex: validatorState.index, + validatorIndex: validator.index, pubKey: pubkeyHex, }); - this.pubkey2index.set(pubkeyHex, validatorState.index); - this.index2pubkey.set(validatorState.index, pubkeyHex); - newIndices.push(validatorState.index); + this.pubkey2index.set(pubkeyHex, validator.index); + this.index2pubkey.set(validator.index, pubkeyHex); + newIndices.push(validator.index); } } // The number of validators that are not in the beacon chain - const pendingCount = pubkeysHex.length - res.response.data.length; + const pendingCount = pubkeysHex.length - validators.length; allValidatorStatuses.set("pending", allValidatorStatuses.getOrDefault("pending") + pendingCount); diff --git a/packages/validator/src/services/prepareBeaconProposer.ts b/packages/validator/src/services/prepareBeaconProposer.ts index 7d7907a4592d..6ae4b0a83870 100644 --- a/packages/validator/src/services/prepareBeaconProposer.ts +++ b/packages/validator/src/services/prepareBeaconProposer.ts @@ -1,5 +1,5 @@ import {Epoch, bellatrix} from "@lodestar/types"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {BeaconConfig} from "@lodestar/config"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; @@ -19,7 +19,7 @@ const REGISTRATION_CHUNK_SIZE = 512; export function pollPrepareBeaconProposer( config: BeaconConfig, logger: LoggerVc, - api: Api, + api: ApiClient, clock: IClock, validatorStore: ValidatorStore, _metrics: Metrics | null @@ -40,11 +40,11 @@ export function pollPrepareBeaconProposer( try { const proposers = indices.map( (index): routes.validator.ProposerPreparationData => ({ - validatorIndex: String(index as number), + validatorIndex: index, feeRecipient: validatorStore.getFeeRecipientByIndex(index), }) ); - ApiError.assert(await api.validator.prepareBeaconProposer(proposers)); + (await api.validator.prepareBeaconProposer({proposers})).assertOk(); logger.debug("Registered proposers with beacon node", {epoch, count: proposers.length}); } catch (e) { logger.error("Failed to register proposers with beacon node", {epoch}, e as Error); @@ -65,7 +65,7 @@ export function pollPrepareBeaconProposer( export function pollBuilderValidatorRegistration( config: BeaconConfig, logger: LoggerVc, - api: Api, + api: ApiClient, clock: IClock, validatorStore: ValidatorStore, _metrics: Metrics | null @@ -102,7 +102,7 @@ export function pollBuilderValidatorRegistration( return validatorStore.getValidatorRegistration(pubkeyHex, {feeRecipient, gasLimit}, slot); }) ); - ApiError.assert(await api.validator.registerValidator(registrations)); + (await api.validator.registerValidator({registrations})).assertOk(); logger.info("Published validator registrations to builder", {epoch, count: registrations.length}); } catch (e) { logger.error("Failed to publish validator registrations to builder", {epoch}, e as Error); diff --git a/packages/validator/src/services/syncCommittee.ts b/packages/validator/src/services/syncCommittee.ts index 9f104e0d7d4b..06926724141c 100644 --- a/packages/validator/src/services/syncCommittee.ts +++ b/packages/validator/src/services/syncCommittee.ts @@ -2,7 +2,7 @@ import {ChainForkConfig} from "@lodestar/config"; import {Slot, CommitteeIndex, altair, Root, BLSSignature} from "@lodestar/types"; import {sleep} from "@lodestar/utils"; import {computeEpochAtSlot, isSyncCommitteeAggregator} from "@lodestar/state-transition"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {IClock, LoggerVc} from "../util/index.js"; import {PubkeyHex} from "../types.js"; import {Metrics} from "../metrics.js"; @@ -26,7 +26,7 @@ export class SyncCommitteeService { constructor( private readonly config: ChainForkConfig, private readonly logger: LoggerVc, - private readonly api: Api, + private readonly api: ApiClient, private readonly clock: IClock, private readonly validatorStore: ValidatorStore, private readonly emitter: ValidatorEventEmitter, @@ -124,10 +124,7 @@ export class SyncCommitteeService { const blockRoot: Uint8Array = this.chainHeaderTracker.getCurrentChainHead(slot) ?? - (await this.api.beacon.getBlockRoot("head").then((res) => { - ApiError.assert(res, "Error producing SyncCommitteeMessage"); - return res.response.data.root; - })); + (await this.api.beacon.getBlockRoot({blockId: "head"})).value().root; const signatures: altair.SyncCommitteeMessage[] = []; @@ -159,7 +156,7 @@ export class SyncCommitteeService { if (signatures.length > 0) { try { - ApiError.assert(await this.api.beacon.submitPoolSyncCommitteeSignatures(signatures)); + (await this.api.beacon.submitPoolSyncCommitteeSignatures({signatures})).assertOk(); this.logger.info("Published SyncCommitteeMessage", {...logCtx, count: signatures.length}); this.metrics?.publishedSyncCommitteeMessage.inc(signatures.length); } catch (e) { @@ -194,8 +191,7 @@ export class SyncCommitteeService { } this.logger.verbose("Producing SyncCommitteeContribution", logCtx); - const res = await this.api.validator.produceSyncCommitteeContribution(slot, subcommitteeIndex, beaconBlockRoot); - ApiError.assert(res, "Error producing sync committee contribution during produceAndPublishAggregates"); + const res = await this.api.validator.produceSyncCommitteeContribution({slot, subcommitteeIndex, beaconBlockRoot}); const signedContributions: altair.SignedContributionAndProof[] = []; @@ -206,7 +202,7 @@ export class SyncCommitteeService { // Produce signed contributions only for validators that are subscribed aggregators. if (selectionProof !== null) { signedContributions.push( - await this.validatorStore.signContributionAndProof(duty, selectionProof, res.response.data) + await this.validatorStore.signContributionAndProof(duty, selectionProof, res.value()) ); this.logger.debug("Signed SyncCommitteeContribution", logCtxValidator); } @@ -220,8 +216,9 @@ export class SyncCommitteeService { if (signedContributions.length > 0) { try { - const res = await this.api.validator.publishContributionAndProofs(signedContributions); - ApiError.assert(res); + ( + await this.api.validator.publishContributionAndProofs({contributionAndProofs: signedContributions}) + ).assertOk(); this.logger.info("Published SyncCommitteeContribution", {...logCtx, count: signedContributions.length}); this.metrics?.publishedSyncCommitteeContribution.inc(signedContributions.length); } catch (e) { @@ -261,7 +258,7 @@ export class SyncCommitteeService { this.logger.debug("Submitting partial sync committee selection proofs", {slot, count: partialSelections.length}); const res = await Promise.race([ - this.api.validator.submitSyncCommitteeSelections(partialSelections), + this.api.validator.submitSyncCommitteeSelections({selections: partialSelections}), // Exit sync committee contributions flow if there is no response after 2/3 of slot. // This is in contrast to attestations aggregations flow which is already exited at 1/3 of the slot // because for sync committee is not required to resubscribe to subnets as beacon node will assume @@ -275,9 +272,8 @@ export class SyncCommitteeService { if (!res) { throw new Error("Failed to receive combined selection proofs before 2/3 of slot"); } - ApiError.assert(res, "Error receiving combined selection proofs"); - const combinedSelections = res.response.data; + const combinedSelections = res.value(); this.logger.debug("Received combined sync committee selection proofs", {slot, count: combinedSelections.length}); for (const dutyAndProofs of duties) { diff --git a/packages/validator/src/services/syncCommitteeDuties.ts b/packages/validator/src/services/syncCommitteeDuties.ts index e4c3fa2a1bed..edc62dea575c 100644 --- a/packages/validator/src/services/syncCommitteeDuties.ts +++ b/packages/validator/src/services/syncCommitteeDuties.ts @@ -3,7 +3,7 @@ import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SYNC_COMMITTEE_SUBNET_SIZE} from "@lod import {computeSyncPeriodAtEpoch, computeSyncPeriodAtSlot, isSyncCommitteeAggregator} from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; import {BLSSignature, Epoch, Slot, SyncPeriod, ValidatorIndex} from "@lodestar/types"; -import {Api, ApiError, routes} from "@lodestar/api"; +import {ApiClient, routes} from "@lodestar/api"; import {IClock, LoggerVc} from "../util/index.js"; import {PubkeyHex} from "../types.js"; import {Metrics} from "../metrics.js"; @@ -77,7 +77,7 @@ export class SyncCommitteeDutiesService { constructor( private readonly config: ChainForkConfig, private readonly logger: LoggerVc, - private readonly api: Api, + private readonly api: ApiClient, clock: IClock, private readonly validatorStore: ValidatorStore, metrics: Metrics | null, @@ -222,8 +222,7 @@ export class SyncCommitteeDutiesService { // If there are any subscriptions, push them out to the beacon node. if (syncCommitteeSubscriptions.length > 0) { // TODO: Should log or throw? - const res = await this.api.validator.prepareSyncCommitteeSubnets(syncCommitteeSubscriptions); - ApiError.assert(res, "Failed to subscribe to sync committee subnets"); + (await this.api.validator.prepareSyncCommitteeSubnets({subscriptions: syncCommitteeSubscriptions})).assertOk(); } } @@ -236,13 +235,12 @@ export class SyncCommitteeDutiesService { return; } - const res = await this.api.validator.getSyncCommitteeDuties(epoch, indexArr); - ApiError.assert(res, "Failed to obtain SyncDuties"); + const duties = (await this.api.validator.getSyncCommitteeDuties({epoch, indices: indexArr})).value(); const dutiesByIndex = new Map(); let count = 0; - for (const duty of res.response.data) { + for (const duty of duties) { const {validatorIndex} = duty; if (!this.validatorStore.hasValidatorIndex(validatorIndex)) { continue; diff --git a/packages/validator/src/validator.ts b/packages/validator/src/validator.ts index 8590fc1d0068..9cb9f2e2d840 100644 --- a/packages/validator/src/validator.ts +++ b/packages/validator/src/validator.ts @@ -3,7 +3,7 @@ import {BLSPubkey, phase0, ssz} from "@lodestar/types"; import {createBeaconConfig, BeaconConfig, ChainForkConfig} from "@lodestar/config"; import {Genesis} from "@lodestar/types/phase0"; import {Logger, toSafePrintableUrl} from "@lodestar/utils"; -import {getClient, Api, routes, ApiError} from "@lodestar/api"; +import {getClient, ApiClient, routes, ApiRequestInit, defaultInit} from "@lodestar/api"; import {computeEpochAtSlot, getCurrentSlot} from "@lodestar/state-transition"; import {Clock, IClock} from "./util/clock.js"; import {waitForGenesis} from "./genesis.js"; @@ -32,7 +32,7 @@ export type ValidatorModules = { attestationService: AttestationService; syncCommitteeService: SyncCommitteeService; config: BeaconConfig; - api: Api; + api: ApiClient; clock: IClock; chainHeaderTracker: ChainHeaderTracker; logger: Logger; @@ -45,7 +45,10 @@ export type ValidatorOptions = { slashingProtection: ISlashingProtection; db: LodestarValidatorDatabaseController; config: ChainForkConfig; - api: Api | string | string[]; + api: { + clientOrUrls: ApiClient | string | string[]; + globalInit?: ApiRequestInit; + }; signers: Signer[]; logger: Logger; processShutdownCallback: ProcessShutdownCallback; @@ -83,7 +86,7 @@ export class Validator { private readonly attestationService: AttestationService; private readonly syncCommitteeService: SyncCommitteeService; private readonly config: BeaconConfig; - private readonly api: Api; + private readonly api: ApiClient; private readonly clock: IClock; private readonly chainHeaderTracker: ChainHeaderTracker; private readonly logger: Logger; @@ -157,22 +160,22 @@ export class Validator { const clock = new Clock(config, logger, {genesisTime: Number(genesis.genesisTime)}); const loggerVc = getLoggerVc(logger, clock); - let api: Api; - if (typeof opts.api === "string" || Array.isArray(opts.api)) { + let api: ApiClient; + const {clientOrUrls, globalInit} = opts.api; + if (typeof clientOrUrls === "string" || Array.isArray(clientOrUrls)) { // This new api instance can make do with default timeout as a faster timeout is // not necessary since this instance won't be used for validator duties api = getClient( { - urls: typeof opts.api === "string" ? [opts.api] : opts.api, + urls: typeof clientOrUrls === "string" ? [clientOrUrls] : clientOrUrls, // Validator would need the beacon to respond within the slot // See https://github.com/ChainSafe/lodestar/issues/5315 for rationale - timeoutMs: config.SECONDS_PER_SLOT * 1000, - getAbortSignal: () => controller.signal, + globalInit: {timeoutMs: config.SECONDS_PER_SLOT * 1000, signal: controller.signal, ...globalInit}, }, {config, logger, metrics: metrics?.restApiClient} ); } else { - api = opts.api; + api = clientOrUrls; } const indicesService = new IndicesService(logger, api, metrics); @@ -270,23 +273,27 @@ export class Validator { static async initializeFromBeaconNode(opts: ValidatorOptions, metrics?: Metrics | null): Promise { const {logger, config} = opts; - let api: Api; - if (typeof opts.api === "string" || Array.isArray(opts.api)) { - const urls = typeof opts.api === "string" ? [opts.api] : opts.api; + let api: ApiClient; + const {clientOrUrls, globalInit} = opts.api; + if (typeof clientOrUrls === "string" || Array.isArray(clientOrUrls)) { + const urls = typeof clientOrUrls === "string" ? [clientOrUrls] : clientOrUrls; // This new api instance can make do with default timeout as a faster timeout is // not necessary since this instance won't be used for validator duties - api = getClient({urls, getAbortSignal: () => opts.abortController.signal}, {config, logger}); - logger.info("Beacon node", {urls: urls.map(toSafePrintableUrl).toString()}); + api = getClient({urls, globalInit: {signal: opts.abortController.signal, ...globalInit}}, {config, logger}); + logger.info("Beacon node", { + urls: urls.map(toSafePrintableUrl).toString(), + requestWireFormat: globalInit?.requestWireFormat ?? defaultInit.requestWireFormat, + responseWireFormat: globalInit?.responseWireFormat ?? defaultInit.responseWireFormat, + }); } else { - api = opts.api; + api = clientOrUrls; } const genesis = await waitForGenesis(api, opts.logger, opts.abortController.signal); logger.info("Genesis fetched from the beacon node"); const res = await api.config.getSpec(); - ApiError.assert(res, "Can not fetch spec from beacon node"); - assertEqualParams(config, res.response.data); + assertEqualParams(config, res.value()); logger.info("Verified connected beacon node and validator have same the config"); await assertEqualGenesis(opts, genesis); @@ -340,7 +347,7 @@ export class Validator { async voluntaryExit(publicKey: string, exitEpoch?: number): Promise { const signedVoluntaryExit = await this.signVoluntaryExit(publicKey, exitEpoch); - ApiError.assert(await this.api.beacon.submitPoolVoluntaryExit(signedVoluntaryExit)); + (await this.api.beacon.submitPoolVoluntaryExit({signedVoluntaryExit})).assertOk(); this.logger.info(`Submitted voluntary exit for ${publicKey} to the network`); } @@ -349,12 +356,10 @@ export class Validator { * Create a signed voluntary exit message for the given validator by its key. */ async signVoluntaryExit(publicKey: string, exitEpoch?: number): Promise { - const res = await this.api.beacon.getStateValidators("head", {id: [publicKey]}); - ApiError.assert(res, "Can not fetch state validators from beacon node"); + const validators = (await this.api.beacon.getStateValidators({stateId: "head", validatorIds: [publicKey]})).value(); - const stateValidators = res.response.data; - const stateValidator = stateValidators[0]; - if (stateValidator === undefined) { + const validator = validators[0]; + if (validator === undefined) { throw new Error(`Validator pubkey ${publicKey} not found in state`); } @@ -362,18 +367,16 @@ export class Validator { exitEpoch = computeEpochAtSlot(getCurrentSlot(this.config, this.clock.genesisTime)); } - return this.validatorStore.signVoluntaryExit(publicKey, stateValidator.index, exitEpoch); + return this.validatorStore.signVoluntaryExit(publicKey, validator.index, exitEpoch); } private async fetchBeaconHealth(): Promise { try { const {status: healthCode} = await this.api.node.getHealth(); - // API always returns http status codes - // Need to find a way to return a custom enum type - if ((healthCode as unknown as routes.node.NodeHealth) === routes.node.NodeHealth.READY) return BeaconHealth.READY; - if ((healthCode as unknown as routes.node.NodeHealth) === routes.node.NodeHealth.SYNCING) - return BeaconHealth.SYNCING; - if ((healthCode as unknown as routes.node.NodeHealth) === routes.node.NodeHealth.NOT_INITIALIZED_OR_ISSUES) + + if (healthCode === routes.node.NodeHealth.READY) return BeaconHealth.READY; + if (healthCode === routes.node.NodeHealth.SYNCING) return BeaconHealth.SYNCING; + if (healthCode === routes.node.NodeHealth.NOT_INITIALIZED_OR_ISSUES) return BeaconHealth.NOT_INITIALIZED_OR_ISSUES; else return BeaconHealth.UNKNOWN; } catch (e) { diff --git a/packages/validator/test/unit/services/attestation.test.ts b/packages/validator/test/unit/services/attestation.test.ts index 397fef20b2ba..e1254d1c6a52 100644 --- a/packages/validator/test/unit/services/attestation.test.ts +++ b/packages/validator/test/unit/services/attestation.test.ts @@ -2,11 +2,11 @@ import {describe, it, expect, beforeAll, beforeEach, afterEach, vi} from "vitest import bls from "@chainsafe/bls"; import {toHexString} from "@chainsafe/ssz"; import {ssz} from "@lodestar/types"; -import {HttpStatusCode, routes} from "@lodestar/api"; +import {routes} from "@lodestar/api"; import {AttestationService, AttestationServiceOpts} from "../../../src/services/attestation.js"; import {AttDutyAndProof} from "../../../src/services/attestationDuties.js"; import {ValidatorStore} from "../../../src/services/validatorStore.js"; -import {getApiClientStub} from "../../utils/apiStub.js"; +import {getApiClientStub, mockApiResponse} from "../../utils/apiStub.js"; import {loggerVc} from "../../utils/logger.js"; import {ClockMock} from "../../utils/clock.js"; import {ChainHeaderTracker} from "../../../src/services/chainHeaderTracker.js"; @@ -85,56 +85,31 @@ describe("AttestationService", function () { ]; // Return empty replies to duties service - api.beacon.getStateValidators.mockResolvedValue({ - response: {executionOptimistic: false, finalized: false, data: []}, - ok: true, - status: HttpStatusCode.OK, - }); - api.validator.getAttesterDuties.mockResolvedValue({ - response: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false, data: []}, - ok: true, - status: HttpStatusCode.OK, - }); + api.beacon.getStateValidators.mockResolvedValue( + mockApiResponse({data: [], meta: {executionOptimistic: false, finalized: false}}) + ); + api.validator.getAttesterDuties.mockResolvedValue( + mockApiResponse({data: [], meta: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false}}) + ); // Mock duties service to return some duties directly vi.spyOn(attestationService["dutiesService"], "getDutiesAtSlot").mockImplementation(() => duties); // Mock beacon's attestation and aggregates endpoints - api.validator.produceAttestationData.mockResolvedValue({ - response: {data: attestation.data}, - ok: true, - status: HttpStatusCode.OK, - }); - api.validator.getAggregatedAttestation.mockResolvedValue({ - response: {data: attestation}, - ok: true, - status: HttpStatusCode.OK, - }); - api.beacon.submitPoolAttestations.mockResolvedValue({ - response: undefined, - ok: true, - status: HttpStatusCode.OK, - }); - api.validator.publishAggregateAndProofs.mockResolvedValue({ - response: undefined, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.produceAttestationData.mockResolvedValue(mockApiResponse({data: attestation.data})); + api.validator.getAggregatedAttestation.mockResolvedValue(mockApiResponse({data: attestation})); + + api.beacon.submitPoolAttestations.mockResolvedValue(mockApiResponse({})); + api.validator.publishAggregateAndProofs.mockResolvedValue(mockApiResponse({})); if (opts.distributedAggregationSelection) { // Mock distributed validator middleware client selections endpoint // and return a selection proof that passes `is_aggregator` test - api.validator.submitBeaconCommitteeSelections.mockResolvedValue({ - response: {data: [{validatorIndex: 0, slot: 0, selectionProof: Buffer.alloc(1, 0x10)}]}, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.submitBeaconCommitteeSelections.mockResolvedValue( + mockApiResponse({data: [{validatorIndex: 0, slot: 0, selectionProof: Buffer.alloc(1, 0x10)}]}) + ); // Accept all subscriptions - api.validator.prepareBeaconCommitteeSubnet.mockResolvedValue({ - response: undefined, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.prepareBeaconCommitteeSubnet.mockResolvedValue(mockApiResponse({})); } // Mock signing service @@ -152,7 +127,7 @@ describe("AttestationService", function () { selectionProof: ZERO_HASH, }; expect(api.validator.submitBeaconCommitteeSelections).toHaveBeenCalledOnce(); - expect(api.validator.submitBeaconCommitteeSelections).toHaveBeenCalledWith([selection]); + expect(api.validator.submitBeaconCommitteeSelections).toHaveBeenCalledWith({selections: [selection]}); // Must resubscribe validator as aggregator on beacon committee subnet const subscription: routes.validator.BeaconCommitteeSubscription = { @@ -163,16 +138,16 @@ describe("AttestationService", function () { isAggregator: true, }; expect(api.validator.prepareBeaconCommitteeSubnet).toHaveBeenCalledOnce(); - expect(api.validator.prepareBeaconCommitteeSubnet).toHaveBeenCalledWith([subscription]); + expect(api.validator.prepareBeaconCommitteeSubnet).toHaveBeenCalledWith({subscriptions: [subscription]}); } // Must submit the attestation received through produceAttestationData() expect(api.beacon.submitPoolAttestations).toHaveBeenCalledOnce(); - expect(api.beacon.submitPoolAttestations).toHaveBeenCalledWith([attestation]); + expect(api.beacon.submitPoolAttestations).toHaveBeenCalledWith({signedAttestations: [attestation]}); // Must submit the aggregate received through getAggregatedAttestation() then createAndSignAggregateAndProof() expect(api.validator.publishAggregateAndProofs).toHaveBeenCalledOnce(); - expect(api.validator.publishAggregateAndProofs).toHaveBeenCalledWith([aggregate]); + expect(api.validator.publishAggregateAndProofs).toHaveBeenCalledWith({signedAggregateAndProofs: [aggregate]}); }); }); } diff --git a/packages/validator/test/unit/services/attestationDuties.test.ts b/packages/validator/test/unit/services/attestationDuties.test.ts index 34924a4c3170..f7154a3a174e 100644 --- a/packages/validator/test/unit/services/attestationDuties.test.ts +++ b/packages/validator/test/unit/services/attestationDuties.test.ts @@ -3,12 +3,12 @@ import {toBufferBE} from "bigint-buffer"; import bls from "@chainsafe/bls"; import {toHexString} from "@chainsafe/ssz"; import {chainConfig} from "@lodestar/config/default"; -import {HttpStatusCode, routes} from "@lodestar/api"; +import {routes} from "@lodestar/api"; import {ssz} from "@lodestar/types"; import {computeEpochAtSlot} from "@lodestar/state-transition"; import {AttestationDutiesService} from "../../../src/services/attestationDuties.js"; import {ValidatorStore} from "../../../src/services/validatorStore.js"; -import {getApiClientStub} from "../../utils/apiStub.js"; +import {getApiClientStub, mockApiResponse} from "../../utils/apiStub.js"; import {loggerVc} from "../../utils/logger.js"; import {ClockMock} from "../../utils/clock.js"; import {initValidatorStore} from "../../utils/validatorStore.js"; @@ -55,11 +55,9 @@ describe("AttestationDutiesService", function () { index, validator: {...defaultValidator.validator, pubkey: pubkeys[0]}, }; - api.beacon.getStateValidators.mockResolvedValue({ - response: {data: [validatorResponse], executionOptimistic: false, finalized: false}, - ok: true, - status: HttpStatusCode.OK, - }); + api.beacon.getStateValidators.mockResolvedValue( + mockApiResponse({data: [validatorResponse], meta: {executionOptimistic: false, finalized: false}}) + ); // Reply with some duties const slot = 1; @@ -73,18 +71,12 @@ describe("AttestationDutiesService", function () { validatorIndex: index, pubkey: pubkeys[0], }; - api.validator.getAttesterDuties.mockResolvedValue({ - response: {dependentRoot: ZERO_HASH_HEX, data: [duty], executionOptimistic: false}, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.getAttesterDuties.mockResolvedValue( + mockApiResponse({data: [duty], meta: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false}}) + ); // Accept all subscriptions - api.validator.prepareBeaconCommitteeSubnet.mockResolvedValue({ - response: undefined, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.prepareBeaconCommitteeSubnet.mockResolvedValue(mockApiResponse({})); // Clock will call runAttesterDutiesTasks() immediately const clock = new ClockMock(); @@ -121,11 +113,9 @@ describe("AttestationDutiesService", function () { index, validator: {...defaultValidator.validator, pubkey: pubkeys[0]}, }; - api.beacon.getStateValidators.mockResolvedValue({ - response: {data: [validatorResponse], executionOptimistic: false, finalized: false}, - ok: true, - status: HttpStatusCode.OK, - }); + api.beacon.getStateValidators.mockResolvedValue( + mockApiResponse({data: [validatorResponse], meta: {executionOptimistic: false, finalized: false}}) + ); // Reply with some duties const slot = 1; @@ -138,18 +128,12 @@ describe("AttestationDutiesService", function () { validatorIndex: index, pubkey: pubkeys[0], }; - api.validator.getAttesterDuties.mockResolvedValue({ - response: {data: [duty], dependentRoot: ZERO_HASH_HEX, executionOptimistic: false}, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.getAttesterDuties.mockResolvedValue( + mockApiResponse({data: [duty], meta: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false}}) + ); // Accept all subscriptions - api.validator.prepareBeaconCommitteeSubnet.mockResolvedValue({ - ok: true, - status: HttpStatusCode.OK, - response: undefined, - }); + api.validator.prepareBeaconCommitteeSubnet.mockResolvedValue(mockApiResponse({})); // Clock will call runAttesterDutiesTasks() immediately const clock = new ClockMock(); diff --git a/packages/validator/test/unit/services/block.test.ts b/packages/validator/test/unit/services/block.test.ts index bcfc57eb8674..6864e62906d1 100644 --- a/packages/validator/test/unit/services/block.test.ts +++ b/packages/validator/test/unit/services/block.test.ts @@ -5,11 +5,11 @@ import {createChainForkConfig} from "@lodestar/config"; import {config as mainnetConfig} from "@lodestar/config/default"; import {sleep} from "@lodestar/utils"; import {ssz, ProducedBlockSource} from "@lodestar/types"; -import {HttpStatusCode, routes} from "@lodestar/api"; +import {routes} from "@lodestar/api"; import {ForkName} from "@lodestar/params"; import {BlockProposingService} from "../../../src/services/block.js"; import {ValidatorStore} from "../../../src/services/validatorStore.js"; -import {getApiClientStub} from "../../utils/apiStub.js"; +import {getApiClientStub, mockApiResponse} from "../../utils/apiStub.js"; import {loggerVc} from "../../utils/logger.js"; import {ClockMock} from "../../utils/clock.js"; import {ZERO_HASH_HEX} from "../../utils/types.js"; @@ -39,15 +39,12 @@ describe("BlockDutiesService", function () { it("Should produce, sign, and publish a block", async function () { // Reply with some duties const slot = 0; // genesisTime is right now, so test with slot = currentSlot - api.validator.getProposerDuties.mockResolvedValue({ - response: { - dependentRoot: ZERO_HASH_HEX, - executionOptimistic: false, - data: [{slot: slot, validatorIndex: 0, pubkey: pubkeys[0]}], - }, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.getProposerDuties.mockResolvedValue( + mockApiResponse({ + data: [{slot, validatorIndex: 0, pubkey: pubkeys[0]}], + meta: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false}, + }) + ); const clock = new ClockMock(); // use produceBlockV3 @@ -71,19 +68,19 @@ describe("BlockDutiesService", function () { validatorStore.getFeeRecipient.mockReturnValue("0x00"); validatorStore.strictFeeRecipientCheck.mockReturnValue(false); - api.validator.produceBlockV3.mockResolvedValue({ - response: { + api.validator.produceBlockV3.mockResolvedValue( + mockApiResponse({ data: signedBlock.message, - version: ForkName.bellatrix, - executionPayloadValue: BigInt(1), - consensusBlockValue: BigInt(1), - executionPayloadBlinded: false, - executionPayloadSource: ProducedBlockSource.engine, - }, - ok: true, - status: HttpStatusCode.OK, - }); - api.beacon.publishBlockV2.mockResolvedValue({ok: true, status: HttpStatusCode.OK, response: undefined}); + meta: { + version: ForkName.bellatrix, + executionPayloadValue: BigInt(1), + consensusBlockValue: BigInt(1), + executionPayloadBlinded: false, + executionPayloadSource: ProducedBlockSource.engine, + }, + }) + ); + api.beacon.publishBlockV2.mockResolvedValue(mockApiResponse({})); // Trigger block production for slot 1 const notifyBlockProductionFn = blockService["dutiesService"]["notifyBlockProductionFn"]; @@ -95,17 +92,16 @@ describe("BlockDutiesService", function () { // Must have submitted the block received on signBlock() expect(api.beacon.publishBlockV2).toHaveBeenCalledOnce(); expect(api.beacon.publishBlockV2.mock.calls[0]).toEqual([ - signedBlock, - {broadcastValidation: routes.beacon.BroadcastValidation.consensus}, + {signedBlockOrContents: signedBlock, broadcastValidation: routes.beacon.BroadcastValidation.consensus}, ]); // ProduceBlockV3 is called with all correct arguments expect(api.validator.produceBlockV3.mock.calls[0]).toEqual([ - 1, - signedBlock.message.body.randaoReveal, - "aaaa", - false, { + slot: 1, + randaoReveal: signedBlock.message.body.randaoReveal, + graffiti: "aaaa", + skipRandaoVerification: false, feeRecipient: "0x00", builderSelection: routes.validator.BuilderSelection.MaxProfit, strictFeeRecipientCheck: false, @@ -118,15 +114,12 @@ describe("BlockDutiesService", function () { it("Should produce, sign, and publish a blinded block", async function () { // Reply with some duties const slot = 0; // genesisTime is right now, so test with slot = currentSlot - api.validator.getProposerDuties.mockResolvedValue({ - response: { - dependentRoot: ZERO_HASH_HEX, - executionOptimistic: false, - data: [{slot: slot, validatorIndex: 0, pubkey: pubkeys[0]}], - }, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.getProposerDuties.mockResolvedValue( + mockApiResponse({ + data: [{slot, validatorIndex: 0, pubkey: pubkeys[0]}], + meta: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false}, + }) + ); const clock = new ClockMock(); // use produceBlockV3 @@ -142,19 +135,19 @@ describe("BlockDutiesService", function () { message: block, signature: signedBlock.signature, })); - api.validator.produceBlockV3.mockResolvedValue({ - response: { + api.validator.produceBlockV3.mockResolvedValue( + mockApiResponse({ data: signedBlock.message, - version: ForkName.bellatrix, - executionPayloadValue: BigInt(1), - consensusBlockValue: BigInt(1), - executionPayloadBlinded: true, - executionPayloadSource: ProducedBlockSource.engine, - }, - ok: true, - status: HttpStatusCode.OK, - }); - api.beacon.publishBlindedBlockV2.mockResolvedValue({ok: true, status: HttpStatusCode.OK, response: undefined}); + meta: { + version: ForkName.bellatrix, + executionPayloadValue: BigInt(1), + consensusBlockValue: BigInt(1), + executionPayloadBlinded: true, + executionPayloadSource: ProducedBlockSource.engine, + }, + }) + ); + api.beacon.publishBlindedBlockV2.mockResolvedValue(mockApiResponse({})); // Trigger block production for slot 1 const notifyBlockProductionFn = blockService["dutiesService"]["notifyBlockProductionFn"]; @@ -166,8 +159,7 @@ describe("BlockDutiesService", function () { // Must have submitted the block received on signBlock() expect(api.beacon.publishBlindedBlockV2).toHaveBeenCalledOnce(); expect(api.beacon.publishBlindedBlockV2.mock.calls[0]).toEqual([ - signedBlock, - {broadcastValidation: routes.beacon.BroadcastValidation.consensus}, + {signedBlindedBlock: signedBlock, broadcastValidation: routes.beacon.BroadcastValidation.consensus}, ]); }); }); diff --git a/packages/validator/test/unit/services/blockDuties.test.ts b/packages/validator/test/unit/services/blockDuties.test.ts index 45dd99a80e77..c1edcc955b2d 100644 --- a/packages/validator/test/unit/services/blockDuties.test.ts +++ b/packages/validator/test/unit/services/blockDuties.test.ts @@ -2,20 +2,17 @@ import {describe, it, expect, beforeAll, beforeEach, afterEach, vi} from "vitest import {toBufferBE} from "bigint-buffer"; import bls from "@chainsafe/bls"; import {toHexString} from "@chainsafe/ssz"; -import {RootHex} from "@lodestar/types"; -import {HttpStatusCode, routes} from "@lodestar/api"; +import {routes} from "@lodestar/api"; import {chainConfig} from "@lodestar/config/default"; import {toHex} from "@lodestar/utils"; import {BlockDutiesService} from "../../../src/services/blockDuties.js"; import {ValidatorStore} from "../../../src/services/validatorStore.js"; -import {getApiClientStub} from "../../utils/apiStub.js"; +import {getApiClientStub, mockApiResponse} from "../../utils/apiStub.js"; import {loggerVc} from "../../utils/logger.js"; import {ClockMock} from "../../utils/clock.js"; import {initValidatorStore} from "../../utils/validatorStore.js"; import {ZERO_HASH_HEX} from "../../utils/types.js"; -type ProposerDutiesRes = {dependentRoot: RootHex; data: routes.validator.ProposerDuty[]}; - describe("BlockDutiesService", function () { const api = getApiClientStub(); let validatorStore: ValidatorStore; @@ -36,15 +33,11 @@ describe("BlockDutiesService", function () { it("Should fetch and persist block duties", async function () { // Reply with some duties const slot = 0; // genesisTime is right now, so test with slot = currentSlot - const duties: ProposerDutiesRes = { - dependentRoot: ZERO_HASH_HEX, - data: [{slot: slot, validatorIndex: 0, pubkey: pubkeys[0]}], - }; - api.validator.getProposerDuties.mockResolvedValue({ - response: {...duties, executionOptimistic: false}, - ok: true, - status: HttpStatusCode.OK, - }); + const duties: routes.validator.ProposerDutyList = [{slot: slot, validatorIndex: 0, pubkey: pubkeys[0]}]; + + api.validator.getProposerDuties.mockResolvedValue( + mockApiResponse({data: duties, meta: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false}}) + ); const notifyBlockProductionFn = vi.fn(); // Returns void @@ -63,7 +56,7 @@ describe("BlockDutiesService", function () { await clock.tickSlotFns(0, controller.signal); // Duties for this epoch should be persisted - expect(Object.fromEntries(dutiesService["proposers"])).toEqual({0: duties}); + expect(Object.fromEntries(dutiesService["proposers"])).toEqual({0: {dependentRoot: ZERO_HASH_HEX, data: duties}}); expect(dutiesService.getblockProposersAtSlot(slot)).toEqual([pubkeys[0]]); @@ -73,14 +66,8 @@ describe("BlockDutiesService", function () { it("Should call notifyBlockProductionFn again on duties re-org", async () => { // A re-org will happen at slot 1 const dependentRootDiff = toHex(Buffer.alloc(32, 1)); - const dutiesBeforeReorg: ProposerDutiesRes = { - dependentRoot: ZERO_HASH_HEX, - data: [{slot: 1, validatorIndex: 0, pubkey: pubkeys[0]}], - }; - const dutiesAfterReorg: ProposerDutiesRes = { - dependentRoot: dependentRootDiff, - data: [{slot: 1, validatorIndex: 1, pubkey: pubkeys[1]}], - }; + const dutiesBeforeReorg: routes.validator.ProposerDutyList = [{slot: 1, validatorIndex: 0, pubkey: pubkeys[0]}]; + const dutiesAfterReorg: routes.validator.ProposerDutyList = [{slot: 1, validatorIndex: 1, pubkey: pubkeys[1]}]; const notifyBlockProductionFn = vi.fn(); // Returns void @@ -97,23 +84,21 @@ describe("BlockDutiesService", function () { ); // Trigger clock onSlot for slot 0 - api.validator.getProposerDuties.mockResolvedValue({ - response: {...dutiesBeforeReorg, executionOptimistic: false}, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.getProposerDuties.mockResolvedValue( + mockApiResponse({data: dutiesBeforeReorg, meta: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false}}) + ); await clock.tickSlotFns(0, controller.signal); // Trigger clock onSlot for slot 1 - Return different duties for slot 1 - api.validator.getProposerDuties.mockResolvedValue({ - response: {...dutiesAfterReorg, executionOptimistic: false}, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.getProposerDuties.mockResolvedValue( + mockApiResponse({data: dutiesAfterReorg, meta: {dependentRoot: dependentRootDiff, executionOptimistic: false}}) + ); await clock.tickSlotFns(1, controller.signal); // Should persist the dutiesAfterReorg - expect(Object.fromEntries(dutiesService["proposers"])).toEqual({0: dutiesAfterReorg}); + expect(Object.fromEntries(dutiesService["proposers"])).toEqual({ + 0: {dependentRoot: dependentRootDiff, data: dutiesAfterReorg}, + }); expect(notifyBlockProductionFn).toBeCalledTimes(2); @@ -124,27 +109,19 @@ describe("BlockDutiesService", function () { it("Should remove signer from duty", async function () { // Reply with some duties const slot = 0; // genesisTime is right now, so test with slot = currentSlot - const duties: ProposerDutiesRes = { - dependentRoot: ZERO_HASH_HEX, - data: [ - {slot: slot, validatorIndex: 0, pubkey: pubkeys[0]}, - {slot: slot, validatorIndex: 1, pubkey: pubkeys[1]}, - {slot: 33, validatorIndex: 2, pubkey: pubkeys[2]}, - ], - }; - - const dutiesRemoved: ProposerDutiesRes = { - dependentRoot: ZERO_HASH_HEX, - data: [ - {slot: slot, validatorIndex: 1, pubkey: pubkeys[1]}, - {slot: 33, validatorIndex: 2, pubkey: pubkeys[2]}, - ], - }; - api.validator.getProposerDuties.mockResolvedValue({ - response: {...duties, executionOptimistic: false}, - ok: true, - status: HttpStatusCode.OK, - }); + const duties: routes.validator.ProposerDutyList = [ + {slot: slot, validatorIndex: 0, pubkey: pubkeys[0]}, + {slot: slot, validatorIndex: 1, pubkey: pubkeys[1]}, + {slot: 33, validatorIndex: 2, pubkey: pubkeys[2]}, + ]; + const dutiesRemoved: routes.validator.ProposerDutyList = [ + {slot: slot, validatorIndex: 1, pubkey: pubkeys[1]}, + {slot: 33, validatorIndex: 2, pubkey: pubkeys[2]}, + ]; + + api.validator.getProposerDuties.mockResolvedValue( + mockApiResponse({data: duties, meta: {dependentRoot: ZERO_HASH_HEX, executionOptimistic: false}}) + ); const notifyBlockProductionFn = vi.fn(); // Returns void @@ -164,12 +141,18 @@ describe("BlockDutiesService", function () { await clock.tickSlotFns(32, controller.signal); // first confirm the duties for the epochs was persisted - expect(Object.fromEntries(dutiesService["proposers"])).toEqual({0: duties, 1: duties}); + expect(Object.fromEntries(dutiesService["proposers"])).toEqual({ + 0: {dependentRoot: ZERO_HASH_HEX, data: duties}, + 1: {dependentRoot: ZERO_HASH_HEX, data: duties}, + }); // then remove a signers public key dutiesService.removeDutiesForKey(toHexString(pubkeys[0])); // confirm that the duties no longer contain the signers public key - expect(Object.fromEntries(dutiesService["proposers"])).toEqual({0: dutiesRemoved, 1: dutiesRemoved}); + expect(Object.fromEntries(dutiesService["proposers"])).toEqual({ + 0: {dependentRoot: ZERO_HASH_HEX, data: dutiesRemoved}, + 1: {dependentRoot: ZERO_HASH_HEX, data: dutiesRemoved}, + }); }); }); diff --git a/packages/validator/test/unit/services/doppelganger.test.ts b/packages/validator/test/unit/services/doppelganger.test.ts index b3943f619494..9b669b3c9396 100644 --- a/packages/validator/test/unit/services/doppelganger.test.ts +++ b/packages/validator/test/unit/services/doppelganger.test.ts @@ -2,10 +2,11 @@ import {describe, it, expect} from "vitest"; import {Epoch, Slot, ValidatorIndex} from "@lodestar/types"; import {sleep} from "@lodestar/utils"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; -import {Api, HttpStatusCode} from "@lodestar/api"; +import {ApiClient} from "@lodestar/api"; import {DoppelgangerService, DoppelgangerStatus} from "../../../src/services/doppelgangerService.js"; import {IndicesService} from "../../../src/services/indices.js"; import {SlashingProtectionMock} from "../../utils/slashingProtectionMock.js"; +import {mockApiResponse} from "../../utils/apiStub.js"; import {testLogger} from "../../utils/logger.js"; import {ClockMock} from "../../utils/clock.js"; @@ -193,26 +194,22 @@ class MapDef extends Map { type LivenessMap = Map>; -function getMockBeaconApi(livenessMap: LivenessMap): Api { +function getMockBeaconApi(livenessMap: LivenessMap): ApiClient { return { validator: { - async getLiveness(epoch, validatorIndices) { - return { - response: { - data: validatorIndices.map((index) => { - const livenessEpoch = livenessMap.get(epoch); - if (!livenessEpoch) throw Error(`Unknown epoch ${epoch}`); - const isLive = livenessEpoch.get(index); - if (isLive === undefined) throw Error(`No liveness for epoch ${epoch} index ${index}`); - return {index, isLive}; - }), - }, - ok: true, - status: HttpStatusCode.OK, - }; + async getLiveness({epoch, indices}) { + return mockApiResponse({ + data: indices.map((index) => { + const livenessEpoch = livenessMap.get(epoch); + if (!livenessEpoch) throw Error(`Unknown epoch ${epoch}`); + const isLive = livenessEpoch.get(index); + if (isLive === undefined) throw Error(`No liveness for epoch ${epoch} index ${index}`); + return {index, isLive}; + }), + }); }, - } as Partial, - } as Partial as Api; + } as Partial, + } as Partial as ApiClient; } class ClockMockMsToSlot extends ClockMock { diff --git a/packages/validator/test/unit/services/syncCommitteDuties.test.ts b/packages/validator/test/unit/services/syncCommitteDuties.test.ts index 75e72cb7a36c..c44360485d09 100644 --- a/packages/validator/test/unit/services/syncCommitteDuties.test.ts +++ b/packages/validator/test/unit/services/syncCommitteDuties.test.ts @@ -5,7 +5,7 @@ import bls from "@chainsafe/bls"; import {toHexString} from "@chainsafe/ssz"; import {createChainForkConfig} from "@lodestar/config"; import {config as mainnetConfig} from "@lodestar/config/default"; -import {HttpStatusCode, routes} from "@lodestar/api"; +import {routes} from "@lodestar/api"; import {ssz} from "@lodestar/types"; import { SyncCommitteeDutiesService, @@ -13,7 +13,7 @@ import { SyncDutySubnet, } from "../../../src/services/syncCommitteeDuties.js"; import {ValidatorStore} from "../../../src/services/validatorStore.js"; -import {getApiClientStub} from "../../utils/apiStub.js"; +import {getApiClientStub, mockApiResponse} from "../../utils/apiStub.js"; import {loggerVc} from "../../utils/logger.js"; import {ClockMock} from "../../utils/clock.js"; import {initValidatorStore} from "../../utils/validatorStore.js"; @@ -59,11 +59,9 @@ describe("SyncCommitteeDutiesService", function () { index: indices[i], validator: {...defaultValidator.validator, pubkey: pubkeys[i]}, })); - api.beacon.getStateValidators.mockResolvedValue({ - response: {data: validatorResponses, executionOptimistic: false, finalized: false}, - ok: true, - status: HttpStatusCode.OK, - }); + api.beacon.getStateValidators.mockResolvedValue( + mockApiResponse({data: validatorResponses, meta: {executionOptimistic: false, finalized: false}}) + ); }); afterEach(() => controller.abort()); @@ -75,18 +73,12 @@ describe("SyncCommitteeDutiesService", function () { validatorIndex: indices[0], validatorSyncCommitteeIndices: [7], }; - api.validator.getSyncCommitteeDuties.mockResolvedValue({ - response: {data: [duty], executionOptimistic: false}, - ok: true, - status: HttpStatusCode.OK, - }); + api.validator.getSyncCommitteeDuties.mockResolvedValue( + mockApiResponse({data: [duty], meta: {executionOptimistic: false}}) + ); // Accept all subscriptions - api.validator.prepareSyncCommitteeSubnets.mockResolvedValue({ - ok: true, - status: HttpStatusCode.OK, - response: undefined, - }); + api.validator.prepareSyncCommitteeSubnets.mockResolvedValue(mockApiResponse({})); // Clock will call runAttesterDutiesTasks() immediately const clock = new ClockMock(); @@ -133,23 +125,23 @@ describe("SyncCommitteeDutiesService", function () { validatorSyncCommitteeIndices: [7], }; when(api.validator.getSyncCommitteeDuties) - .calledWith(0, expect.any(Array)) - .thenResolve({response: {data: [duty], executionOptimistic: false}, ok: true, status: HttpStatusCode.OK}); + .calledWith({epoch: 0, indices}) + .thenResolve(mockApiResponse({data: [duty], meta: {executionOptimistic: false}})); // sync period 1 should all return empty when(api.validator.getSyncCommitteeDuties) - .calledWith(256, expect.any(Array)) - .thenResolve({response: {data: [], executionOptimistic: false}, ok: true, status: HttpStatusCode.OK}); + .calledWith({epoch: 256, indices}) + .thenResolve(mockApiResponse({data: [], meta: {executionOptimistic: false}})); when(api.validator.getSyncCommitteeDuties) - .calledWith(257, expect.any(Array)) - .thenResolve({response: {data: [], executionOptimistic: false}, ok: true, status: HttpStatusCode.OK}); + .calledWith({epoch: 257, indices}) + .thenResolve(mockApiResponse({data: [], meta: {executionOptimistic: false}})); const duty2: routes.validator.SyncDuty = { pubkey: pubkeys[1], validatorIndex: indices[1], validatorSyncCommitteeIndices: [5], }; when(api.validator.getSyncCommitteeDuties) - .calledWith(1, expect.any(Array)) - .thenResolve({response: {data: [duty2], executionOptimistic: false}, ok: true, status: HttpStatusCode.OK}); + .calledWith({epoch: 1, indices}) + .thenResolve(mockApiResponse({data: [duty2], meta: {executionOptimistic: false}})); // Clock will call runAttesterDutiesTasks() immediately const clock = new ClockMock(); @@ -197,15 +189,12 @@ describe("SyncCommitteeDutiesService", function () { validatorSyncCommitteeIndices: [7], }; when(api.validator.getSyncCommitteeDuties) - .calledWith(expect.any(Number), expect.any(Array)) - .thenResolve({response: {data: [duty1, duty2], executionOptimistic: false}, ok: true, status: HttpStatusCode.OK}); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + .calledWith({epoch: expect.any(Number), indices}) + .thenResolve(mockApiResponse({data: [duty1, duty2], meta: {executionOptimistic: false}})); // Accept all subscriptions - api.validator.prepareSyncCommitteeSubnets.mockResolvedValue({ - ok: true, - status: HttpStatusCode.OK, - response: undefined, - }); + api.validator.prepareSyncCommitteeSubnets.mockResolvedValue(mockApiResponse({})); // Clock will call runAttesterDutiesTasks() immediately const clock = new ClockMock(); diff --git a/packages/validator/test/unit/services/syncCommittee.test.ts b/packages/validator/test/unit/services/syncCommittee.test.ts index 57870da94dc3..b6cba32fc96b 100644 --- a/packages/validator/test/unit/services/syncCommittee.test.ts +++ b/packages/validator/test/unit/services/syncCommittee.test.ts @@ -4,11 +4,11 @@ import {toHexString} from "@chainsafe/ssz"; import {createChainForkConfig} from "@lodestar/config"; import {config as mainnetConfig} from "@lodestar/config/default"; import {ssz} from "@lodestar/types"; -import {HttpStatusCode, routes} from "@lodestar/api"; +import {routes} from "@lodestar/api"; import {SyncCommitteeService, SyncCommitteeServiceOpts} from "../../../src/services/syncCommittee.js"; import {SyncDutyAndProofs} from "../../../src/services/syncCommitteeDuties.js"; import {ValidatorStore} from "../../../src/services/validatorStore.js"; -import {getApiClientStub} from "../../utils/apiStub.js"; +import {getApiClientStub, mockApiResponse} from "../../utils/apiStub.js"; import {loggerVc} from "../../utils/logger.js"; import {ClockMock} from "../../utils/clock.js"; import {ChainHeaderTracker} from "../../../src/services/chainHeaderTracker.js"; @@ -97,16 +97,12 @@ describe("SyncCommitteeService", function () { ]; // Return empty replies to duties service - api.beacon.getStateValidators.mockResolvedValue({ - response: {data: [], executionOptimistic: false, finalized: false}, - ok: true, - status: HttpStatusCode.OK, - }); - api.validator.getSyncCommitteeDuties.mockResolvedValue({ - response: {data: [], executionOptimistic: false}, - ok: true, - status: HttpStatusCode.OK, - }); + api.beacon.getStateValidators.mockResolvedValue( + mockApiResponse({data: [], meta: {executionOptimistic: false, finalized: false}}) + ); + api.validator.getSyncCommitteeDuties.mockResolvedValue( + mockApiResponse({data: [], meta: {executionOptimistic: false}}) + ); // Mock duties service to return some duties directly vi.spyOn(syncCommitteeService["dutiesService"], "getDutiesAtSlot").mockResolvedValue(duties); @@ -114,32 +110,18 @@ describe("SyncCommitteeService", function () { // Mock beacon's sync committee and contribution routes chainHeaderTracker.getCurrentChainHead.mockReturnValue(beaconBlockRoot); - api.beacon.submitPoolSyncCommitteeSignatures.mockResolvedValue({ - response: undefined, - ok: true, - status: HttpStatusCode.OK, - }); - api.validator.produceSyncCommitteeContribution.mockResolvedValue({ - response: {data: contribution}, - ok: true, - status: HttpStatusCode.OK, - }); - api.validator.publishContributionAndProofs.mockResolvedValue({ - response: undefined, - ok: true, - status: HttpStatusCode.OK, - }); + api.beacon.submitPoolSyncCommitteeSignatures.mockResolvedValue(mockApiResponse({})); + api.validator.produceSyncCommitteeContribution.mockResolvedValue(mockApiResponse({data: contribution})); + api.validator.publishContributionAndProofs.mockResolvedValue(mockApiResponse({})); if (opts.distributedAggregationSelection) { // Mock distributed validator middleware client selections endpoint // and return a selection proof that passes `is_sync_committee_aggregator` test - api.validator.submitSyncCommitteeSelections.mockResolvedValue({ - response: { + api.validator.submitSyncCommitteeSelections.mockResolvedValue( + mockApiResponse({ data: [{validatorIndex: 0, slot: 0, subcommitteeIndex: 0, selectionProof: Buffer.alloc(1, 0x19)}], - }, - ok: true, - status: HttpStatusCode.OK, - }); + }) + ); } // Mock signing service @@ -158,16 +140,20 @@ describe("SyncCommitteeService", function () { selectionProof: ZERO_HASH, }; expect(api.validator.submitSyncCommitteeSelections).toHaveBeenCalledOnce(); - expect(api.validator.submitSyncCommitteeSelections).toHaveBeenCalledWith([selection]); + expect(api.validator.submitSyncCommitteeSelections).toHaveBeenCalledWith({selections: [selection]}); } // Must submit the signature received through signSyncCommitteeSignature() expect(api.beacon.submitPoolSyncCommitteeSignatures).toHaveBeenCalledOnce(); - expect(api.beacon.submitPoolSyncCommitteeSignatures).toHaveBeenCalledWith([syncCommitteeSignature]); + expect(api.beacon.submitPoolSyncCommitteeSignatures).toHaveBeenCalledWith({ + signatures: [syncCommitteeSignature], + }); // Must submit the aggregate received through produceSyncCommitteeContribution() then signContributionAndProof() expect(api.validator.publishContributionAndProofs).toHaveBeenCalledOnce(); - expect(api.validator.publishContributionAndProofs).toHaveBeenCalledWith([contributionAndProof]); + expect(api.validator.publishContributionAndProofs).toHaveBeenCalledWith({ + contributionAndProofs: [contributionAndProof], + }); }); }); } diff --git a/packages/validator/test/utils/apiStub.ts b/packages/validator/test/utils/apiStub.ts index 521abfae171d..0ee39662952f 100644 --- a/packages/validator/test/utils/apiStub.ts +++ b/packages/validator/test/utils/apiStub.ts @@ -1,7 +1,7 @@ import {vi, Mocked} from "vitest"; -import {Api} from "@lodestar/api"; +import {ApiClientMethods, ApiResponse, Endpoint, Endpoints, HttpStatusCode} from "@lodestar/api"; -export function getApiClientStub(): {[K in keyof Api]: Mocked} { +export function getApiClientStub(): {[K in keyof Endpoints]: Mocked>} { return { beacon: { getStateValidators: vi.fn(), @@ -25,5 +25,17 @@ export function getApiClientStub(): {[K in keyof Api]: Mocked} { publishAggregateAndProofs: vi.fn(), submitBeaconCommitteeSelections: vi.fn(), }, - } as unknown as {[K in keyof Api]: Mocked}; + } as unknown as {[K in keyof Endpoints]: Mocked>}; +} + +export function mockApiResponse>({ + data, + meta, +}: (E["return"] extends void ? {data?: never} : {data: E["return"]}) & + (E["meta"] extends void ? {meta?: never} : {meta: E["meta"]})): ApiResponse { + const response = new Response(null, {status: HttpStatusCode.OK}); + const apiResponse = new ApiResponse({} as any, null, response); + apiResponse.value = () => data as T; + apiResponse.meta = () => meta as M; + return apiResponse; } diff --git a/packages/validator/test/utils/validatorStore.ts b/packages/validator/test/utils/validatorStore.ts index 61de1e9371d6..5fe530ea0cfe 100644 --- a/packages/validator/test/utils/validatorStore.ts +++ b/packages/validator/test/utils/validatorStore.ts @@ -1,5 +1,5 @@ import {SecretKey} from "@chainsafe/bls/types"; -import {Api} from "@lodestar/api"; +import {ApiClient} from "@lodestar/api"; import {chainConfig} from "@lodestar/config/default"; import {createBeaconConfig, ChainConfig} from "@lodestar/config"; import {Signer, SignerType, ValidatorStore} from "../../src/index.js"; @@ -13,7 +13,7 @@ import {SlashingProtectionMock} from "./slashingProtectionMock.js"; */ export async function initValidatorStore( secretKeys: SecretKey[], - api: Api, + api: ApiClient, customChainConfig: ChainConfig = chainConfig, valProposerConfig: ValidatorProposerConfig = {defaultConfig: {builder: {}}, proposerConfig: {}} ): Promise { From 90b053cd742150682c0c3567dc47a2fbfc44a62d Mon Sep 17 00:00:00 2001 From: twoeths Date: Mon, 10 Jun 2024 21:22:40 +0700 Subject: [PATCH 10/55] chore: enable nHistoricalStates e2e test (#6867) --- .../stateCache/nHistoricalStates.test.ts | 19 +++++-- .../{forkchoice.ts => fork-choice/reorg.ts} | 52 ++++++++----------- 2 files changed, 37 insertions(+), 34 deletions(-) rename packages/beacon-node/test/mocks/{forkchoice.ts => fork-choice/reorg.ts} (76%) 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 170081e4dcd8..54007423e821 100644 --- a/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts +++ b/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts @@ -12,7 +12,7 @@ import {waitForEvent} from "../../../utils/events/resolver.js"; import {ChainEvent, ReorgEventData} from "../../../../src/chain/emitter.js"; import {connect, onPeerConnect} from "../../../utils/network.js"; import {CacheItemType} from "../../../../src/chain/stateCache/types.js"; -import {ReorgedForkChoice} from "../../../mocks/forkchoice.js"; +import {ReorgedForkChoice} from "../../../mocks/fork-choice/reorg.js"; /** * Test different reorg scenarios to make sure the StateCache implementations are correct. @@ -249,8 +249,6 @@ describe( // 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 - // TODO: remove this after proposer boost reorg is fully implemented - skip: true, }, ]; @@ -295,6 +293,8 @@ describe( chain: { blsVerifyAllMainThread: true, forkchoiceConstructor: ReorgedForkChoice, + // this node does not need to reload state + nHistoricalStates: false, proposerBoost: true, }, }, @@ -315,6 +315,7 @@ describe( chain: { blsVerifyAllMainThread: true, forkchoiceConstructor: ReorgedForkChoice, + // this node can follow with nHistoricalStates flag and it has to reload state nHistoricalStates: true, maxBlockStates, maxCPStateEpochsInMemory, @@ -347,9 +348,16 @@ describe( 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, 240000, (cp) => cp.epoch === 3) + waitForEvent( + bn.chain.emitter, + ChainEvent.checkpoint, + (cpSlot + genesisSlotsDelay + 1) * testParams.SECONDS_PER_SLOT * 1000, + (cp) => cp.epoch === cpEpoch + ) ) ); expect(checkpoints[0]).toEqual(checkpoints[1]); @@ -369,7 +377,8 @@ describe( waitForEvent( bn.chain.emitter, routes.events.EventType.chainReorg, - 240000, + // reorged event happens at reorgedSlot + 1 + (reorgedSlot + 1 - cpSlot + 1) * testParams.SECONDS_PER_SLOT * 1000, (reorgData) => reorgData.slot === reorgedSlot + 1 ) ) diff --git a/packages/beacon-node/test/mocks/forkchoice.ts b/packages/beacon-node/test/mocks/fork-choice/reorg.ts similarity index 76% rename from packages/beacon-node/test/mocks/forkchoice.ts rename to packages/beacon-node/test/mocks/fork-choice/reorg.ts index 5ec594e4ed81..6980b6f5a92a 100644 --- a/packages/beacon-node/test/mocks/forkchoice.ts +++ b/packages/beacon-node/test/mocks/fork-choice/reorg.ts @@ -1,5 +1,6 @@ import {ChainForkConfig} from "@lodestar/config"; import {ForkChoice, ForkChoiceOpts, IForkChoiceStore, ProtoArray, ProtoBlock} from "@lodestar/fork-choice"; +import {NotReorgedReason} from "@lodestar/fork-choice/lib/forkChoice/interface.js"; import {Slot} from "@lodestar/types"; /** @@ -22,10 +23,6 @@ export class ReorgedForkChoice extends ForkChoice { reorgedSlot: Slot | undefined; reorgDistance: number | undefined; private readonly _fcStore: IForkChoiceStore; - // these flags to mark if the current call of getHead() is to produce a block - // the other way to check this is to check the n-th call of getHead() in the same slot, but this is easier - private calledUpdateHead = false; - private calledUpdateTime = false; constructor( config: ChainForkConfig, @@ -38,33 +35,40 @@ export class ReorgedForkChoice extends ForkChoice { this._fcStore = fcStore; } + /** + * Override to trigger reorged event at `reorgedSlot + 1` + */ + getProposerHead( + headBlock: ProtoBlock, + secFromSlot: number, + slot: Slot + ): {proposerHead: ProtoBlock; isHeadTimely: boolean; notReorgedReason?: NotReorgedReason} { + const currentSlot = this._fcStore.currentSlot; + if (this.reorgedSlot !== undefined && this.reorgDistance !== undefined && currentSlot === this.reorgedSlot + 1) { + const nodes = super.getAllNodes(); + const headSlot = currentSlot - this.reorgDistance; + const headNode = nodes.find((node) => node.slot === headSlot); + if (headNode !== undefined) { + return {proposerHead: headNode, isHeadTimely: true}; + } + } + + return super.getProposerHead(headBlock, secFromSlot, slot); + } + /** * Override the getHead() method - * - produceBlock: to reorg at a given slot and distance. * - produceAttestation: to build on the latest node after the reorged slot * - importBlock: to return the old branch at the reorged slot to produce the reorg event */ getHead = (): ProtoBlock => { const currentSlot = this._fcStore.currentSlot; - const producingBlock = this.calledUpdateHead && this.calledUpdateTime; if (this.reorgedSlot === undefined || this.reorgDistance === undefined) { return super.getHead(); } - this.calledUpdateTime = false; - this.calledUpdateHead = false; - - // produceBlock: at reorgedSlot + 1, build new branch - if (currentSlot === this.reorgedSlot + 1 && producingBlock) { - const nodes = super.getAllNodes(); - const headSlot = currentSlot - this.reorgDistance; - const headNode = nodes.find((node) => node.slot === headSlot); - if (headNode !== undefined) { - return headNode; - } - } - // this is mainly for producing attestations + produceBlock for latter slots + // at `reorgedSlot + 1` should return the old head to trigger reorg event if (currentSlot > this.reorgedSlot + 1) { // from now on build on latest node which reorged at the given slot const nodes = super.getAllNodes(); @@ -75,12 +79,6 @@ export class ReorgedForkChoice extends ForkChoice { return super.getHead(); }; - updateTime(currentSlot: Slot): void { - // set flag to signal produceBlock flow - this.calledUpdateTime = true; - super.updateTime(currentSlot); - } - /** * Override this function to: * - produceBlock flow: mark flags to indicate that the current call of getHead() is to produce a block @@ -90,10 +88,6 @@ export class ReorgedForkChoice extends ForkChoice { if (this.reorgedSlot === undefined || this.reorgDistance === undefined) { return super.updateHead(); } - // in all produce blocks flow, it always call updateTime() first then recomputeForkChoiceHead() - if (this.calledUpdateTime) { - this.calledUpdateHead = true; - } const currentSlot = this._fcStore.currentSlot; if (currentSlot <= this.reorgedSlot) { return super.updateHead(); From 4ef27accc9d51eca9f3f7dd9c6b9877cfb597a60 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 10 Jun 2024 15:23:13 +0100 Subject: [PATCH 11/55] chore: remove no longer supported networks from config (#6870) * chore: remove no longer supported networks from config * Fix spelling of lighthouse --- .wordlist.txt | 2 - default.env | 2 +- packages/beacon-node/src/metrics/options.ts | 2 +- packages/cli/docsgen/markdown.ts | 18 +- packages/cli/src/cmds/beacon/index.ts | 4 +- packages/cli/src/cmds/lightclient/index.ts | 4 +- packages/cli/src/cmds/validator/import.ts | 2 +- packages/cli/src/cmds/validator/index.ts | 4 +- .../validator/slashingProtection/export.ts | 2 +- .../validator/slashingProtection/import.ts | 2 +- .../cli/src/cmds/validator/voluntaryExit.ts | 4 +- packages/cli/src/networks/goerli.ts | 15 - packages/cli/src/networks/index.ts | 19 +- packages/cli/src/networks/ropsten.ts | 9 - .../cli/test/unit/config/beaconParams.test.ts | 6 +- .../cli/test/unit/paths/globalPaths.test.ts | 4 +- .../config/src/chainConfig/networks/goerli.ts | 43 -- .../src/chainConfig/networks/ropsten.ts | 36 -- packages/config/src/chainConfig/types.ts | 2 +- packages/config/src/networks.ts | 16 +- packages/flare/src/cmds/selfSlashAttester.ts | 2 +- packages/flare/src/cmds/selfSlashProposer.ts | 2 +- .../light-client/test/utils/getGenesisData.ts | 2 +- packages/prover/src/utils/execution.ts | 2 - packages/validator/src/metrics.ts | 2 +- .../test/unit/utils/interopConfigs.ts | 371 ++++++++++++------ .../validator/test/unit/utils/params.test.ts | 12 +- 27 files changed, 295 insertions(+), 294 deletions(-) delete mode 100644 packages/cli/src/networks/goerli.ts delete mode 100644 packages/cli/src/networks/ropsten.ts delete mode 100644 packages/config/src/chainConfig/networks/goerli.ts delete mode 100644 packages/config/src/chainConfig/networks/ropsten.ts diff --git a/.wordlist.txt b/.wordlist.txt index 06622ba04b3e..dbb4f707af72 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -139,7 +139,6 @@ flamegraph flamegraphs getNetworkIdentity gnosis -goerli heapdump heaptrack holesky @@ -182,7 +181,6 @@ repos req reqresp responder -ropsten runtime scalability secp diff --git a/default.env b/default.env index 80b978f13a7c..d5164833c1af 100644 --- a/default.env +++ b/default.env @@ -1,5 +1,5 @@ # To specify a specific network (defaults to mainnet) set this value. -# Allowed values are: mainnet, gnosis, goerli, ropsten, sepolia and chiado. Source for currently supported networks: https://github.com/ChainSafe/lodestar/blob/unstable/packages/cli/src/networks/index.ts#L19 +# Allowed values are: mainnet, gnosis, holesky, sepolia and chiado. Source for currently supported networks: https://github.com/ChainSafe/lodestar/blob/unstable/packages/cli/src/networks/index.ts#L21 LODESTAR_NETWORK=mainnet # Set a custom admin password to prevent having to signup. diff --git a/packages/beacon-node/src/metrics/options.ts b/packages/beacon-node/src/metrics/options.ts index 1abfb6c7ffd1..976b907d2322 100644 --- a/packages/beacon-node/src/metrics/options.ts +++ b/packages/beacon-node/src/metrics/options.ts @@ -6,7 +6,7 @@ export type LodestarMetadata = { version: string; /** "4f816b16dfde718e2d74f95f2c8292596138c248" */ commit: string; - /** "goerli" */ + /** "holesky" */ network: string; }; diff --git a/packages/cli/docsgen/markdown.ts b/packages/cli/docsgen/markdown.ts index fc2bf164ca8d..5c3033e9c0df 100644 --- a/packages/cli/docsgen/markdown.ts +++ b/packages/cli/docsgen/markdown.ts @@ -5,7 +5,11 @@ const DEFAULT_SEPARATOR = "\n\n"; const LINE_BREAK = "\n\n"; function sanitizeDescription(description: string): string { - return description.replaceAll("<", "<").replaceAll(">", ">").replaceAll("{", "{").replaceAll("}", "}"); + return description + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll("{", "{") + .replaceAll("}", "}"); } function renderExampleBody(example: CliExample, lodestarCommand?: string): string { @@ -27,10 +31,10 @@ ${lodestarCommand ? `${lodestarCommand} ` : ""}${example.command} * ------------------- * #### Basic `validator` command example * - * Run one validator client with all the keystores available in the directory .goerli/keystores + * Run one validator client with all the keystores available in the directory .holesky/keystores * * ``` - * validator --network goerli + * validator --network holesky * ``` * ------------------- */ @@ -46,15 +50,15 @@ function renderCommandExample(example: CliExample, lodestarCommand?: string): st * * #### Basic `validator` command example * - * Run one validator client with all the keystores available in the directory .goerli/keystores + * Run one validator client with all the keystores available in the directory .holesky/keystores * * ``` - * validator --network goerli + * validator --network holesky * ``` * * #### Advanced `validator` command example * - * Run one validator client with all the keystores available in the directory .goerli/keystores + * Run one validator client with all the keystores available in the directory .holesky/keystores * using an rcConfig file for configuration * * ``` @@ -183,7 +187,7 @@ The following sub-commands are available with the \`${command}\` command:`, * Import an interchange file to the slashing protection DB * * ``` - * ./lodestar validator slashing-protection import --network goerli --file interchange.json + * ./lodestar validator slashing-protection import --network holesky --file interchange.json * ``` */ function renderSubCommand(sub: SubCommandDefinition, lodestarCommand?: string): string { diff --git a/packages/cli/src/cmds/beacon/index.ts b/packages/cli/src/cmds/beacon/index.ts index 051fe10c2668..d1d91b9fe424 100644 --- a/packages/cli/src/cmds/beacon/index.ts +++ b/packages/cli/src/cmds/beacon/index.ts @@ -9,8 +9,8 @@ export const beacon: CliCommand = { docsFolder: "run/beacon-management", examples: [ { - command: "beacon --network goerli", - description: "Run a beacon chain node and connect to the goerli testnet", + command: "beacon --network holesky", + description: "Run a beacon chain node and connect to the holesky testnet", }, ], options: beaconOptions as CliCommandOptions, diff --git a/packages/cli/src/cmds/lightclient/index.ts b/packages/cli/src/cmds/lightclient/index.ts index 6ee74e7d960f..bf85df50800f 100644 --- a/packages/cli/src/cmds/lightclient/index.ts +++ b/packages/cli/src/cmds/lightclient/index.ts @@ -9,8 +9,8 @@ export const lightclient: CliCommand = { docsFolder: "libraries/lightclient-prover", examples: [ { - command: "lightclient --network goerli", - description: "Run lightclient with goerli network", + command: "lightclient --network holesky", + description: "Run lightclient with holesky network", }, ], options: lightclientOptions, diff --git a/packages/cli/src/cmds/validator/import.ts b/packages/cli/src/cmds/validator/import.ts index 58ae8a033de6..48e5205549f1 100644 --- a/packages/cli/src/cmds/validator/import.ts +++ b/packages/cli/src/cmds/validator/import.ts @@ -26,7 +26,7 @@ Ethereum Foundation utility.", examples: [ { - command: "validator import --network goerli --importKeystores $HOME/staking-deposit-cli/validator_keys", + command: "validator import --network holesky --importKeystores $HOME/staking-deposit-cli/validator_keys", description: "Import validator keystores generated with the Ethereum Foundation Staking Launchpad", }, ], diff --git a/packages/cli/src/cmds/validator/index.ts b/packages/cli/src/cmds/validator/index.ts index f1ee2a458a3a..d0c7ea86c042 100644 --- a/packages/cli/src/cmds/validator/index.ts +++ b/packages/cli/src/cmds/validator/index.ts @@ -15,11 +15,11 @@ export const validator: CliCommand = { docsFolder: "run/validator-management", examples: [ { - command: "validator --network goerli", + command: "validator --network holesky", title: "Base `validator` command", description: "Run one validator client with all the keystores available in the directory" + - ` ${getAccountPaths({dataDir: ".goerli"}, "goerli").keystoresDir}`, + ` ${getAccountPaths({dataDir: ".holesky"}, "holesky").keystoresDir}`, }, ], options: validatorOptions, diff --git a/packages/cli/src/cmds/validator/slashingProtection/export.ts b/packages/cli/src/cmds/validator/slashingProtection/export.ts index fb694feb058a..7d1a4f8e6e2f 100644 --- a/packages/cli/src/cmds/validator/slashingProtection/export.ts +++ b/packages/cli/src/cmds/validator/slashingProtection/export.ts @@ -26,7 +26,7 @@ export const exportCmd: CliCommand { const GENESIS_FORK_VERSION_MAINNET = "0x00000000"; - const GENESIS_FORK_VERSION_GOERLI = "0x00001020"; + const GENESIS_FORK_VERSION_HOLESKY = "0x01017000"; const GENESIS_FORK_VERSION_FILE = "0x00009902"; const GENESIS_FORK_VERSION_CLI = "0x00009903"; - const networkName = "goerli"; + const networkName = "holesky"; const paramsFilepath = getTestdirPath("./test-config.yaml"); const testCases: { @@ -34,7 +34,7 @@ describe("config / beaconParams", () => { additionalParamsCli: {}, }, // eslint-disable-next-line @typescript-eslint/naming-convention - GENESIS_FORK_VERSION: GENESIS_FORK_VERSION_GOERLI, + GENESIS_FORK_VERSION: GENESIS_FORK_VERSION_HOLESKY, }, { id: "Params from network & file > returns file", diff --git a/packages/cli/test/unit/paths/globalPaths.test.ts b/packages/cli/test/unit/paths/globalPaths.test.ts index 98de8fd87a79..0d301bb56ffb 100644 --- a/packages/cli/test/unit/paths/globalPaths.test.ts +++ b/packages/cli/test/unit/paths/globalPaths.test.ts @@ -20,9 +20,9 @@ describe("paths / global", () => { }, { id: "Network paths", - args: {network: "goerli"}, + args: {network: "holesky"}, globalPaths: { - dataDir: "/my-root-dir/lodestar/goerli", + dataDir: "/my-root-dir/lodestar/holesky", }, }, { diff --git a/packages/config/src/chainConfig/networks/goerli.ts b/packages/config/src/chainConfig/networks/goerli.ts deleted file mode 100644 index b90f79f4e272..000000000000 --- a/packages/config/src/chainConfig/networks/goerli.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; -import {ChainConfig} from "../types.js"; -import {chainConfig as mainnet} from "../configs/mainnet.js"; - -// Goerli beacon chain config: -// https://github.com/eth-clients/goerli/blob/main/prater/config.yaml - -export const goerliChainConfig: ChainConfig = { - ...mainnet, - - CONFIG_NAME: "goerli", - - // Ethereum Goerli testnet - DEPOSIT_CHAIN_ID: 5, - DEPOSIT_NETWORK_ID: 5, - // Prater test deposit contract on Goerli Testnet - DEPOSIT_CONTRACT_ADDRESS: b("0xff50ed3d0ec03aC01D4C79aAd74928BFF48a7b2b"), - - // Mar-01-2021 08:53:32 AM +UTC - MIN_GENESIS_TIME: 1614588812, - // Prater area code (Vienna) - GENESIS_FORK_VERSION: b("0x00001020"), - // Customized for Prater: 1919188 seconds (Mar-23-2021 02:00:00 PM +UTC) - GENESIS_DELAY: 1919188, - - // Transition - // Expected August 10, 2022 - TERMINAL_TOTAL_DIFFICULTY: BigInt("10790000"), - - // Forking - ALTAIR_FORK_VERSION: b("0x01001020"), - ALTAIR_FORK_EPOCH: 36660, - // Bellatrix - BELLATRIX_FORK_VERSION: b("0x02001020"), - BELLATRIX_FORK_EPOCH: 112260, - // Capella - CAPELLA_FORK_VERSION: b("0x03001020"), - CAPELLA_FORK_EPOCH: 162304, - // Deneb - DENEB_FORK_VERSION: b("0x04001020"), - DENEB_FORK_EPOCH: 231680, -}; diff --git a/packages/config/src/chainConfig/networks/ropsten.ts b/packages/config/src/chainConfig/networks/ropsten.ts deleted file mode 100644 index 6bb7811ccba3..000000000000 --- a/packages/config/src/chainConfig/networks/ropsten.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; -import {ChainConfig} from "../types.js"; -import {chainConfig as mainnet} from "../configs/mainnet.js"; - -// Ropsten beacon chain config: -// https://github.com/eth-clients/merge-testnets/blob/main/ropsten-beacon-chain/config.yaml - -export const ropstenChainConfig: ChainConfig = { - ...mainnet, - - CONFIG_NAME: "ropsten", - - // Genesis - // --------------------------------------------------------------- - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 100000, - // # Monday, May 30th, 2022 3:00:00 PM +UTC - MIN_GENESIS_TIME: 1653318000, - GENESIS_FORK_VERSION: b("0x80000069"), - - // Forking - // --------------------------------------------------------------- - // # Altair - ALTAIR_FORK_VERSION: b("0x80000070"), - ALTAIR_FORK_EPOCH: 500, - // # Merge - BELLATRIX_FORK_VERSION: b("0x80000071"), - BELLATRIX_FORK_EPOCH: 750, - TERMINAL_TOTAL_DIFFICULTY: BigInt("50000000000000000"), - - // Deposit contract - // --------------------------------------------------------------- - DEPOSIT_CHAIN_ID: 3, - DEPOSIT_NETWORK_ID: 3, - DEPOSIT_CONTRACT_ADDRESS: b("0x6f22fFbC56eFF051aECF839396DD1eD9aD6BBA9D"), -}; diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 657c8a6c14b4..45f05bfaa724 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -11,7 +11,7 @@ export type ChainConfig = { * Free-form short name of the network that this configuration applies to - known * canonical network names include: * * 'mainnet' - there can be only one - * * 'prater' - testnet + * * 'holesky' - testnet * Must match the regex: [a-z0-9\-] */ CONFIG_NAME: string; diff --git a/packages/config/src/networks.ts b/packages/config/src/networks.ts index 8ff3cdd15256..819c02b995b0 100644 --- a/packages/config/src/networks.ts +++ b/packages/config/src/networks.ts @@ -1,8 +1,6 @@ import {ChainConfig} from "./chainConfig/index.js"; import {mainnetChainConfig} from "./chainConfig/networks/mainnet.js"; import {gnosisChainConfig} from "./chainConfig/networks/gnosis.js"; -import {goerliChainConfig} from "./chainConfig/networks/goerli.js"; -import {ropstenChainConfig} from "./chainConfig/networks/ropsten.js"; import {sepoliaChainConfig} from "./chainConfig/networks/sepolia.js"; import {holeskyChainConfig} from "./chainConfig/networks/holesky.js"; import {chiadoChainConfig} from "./chainConfig/networks/chiado.js"; @@ -11,20 +9,16 @@ import {ephemeryChainConfig} from "./chainConfig/networks/ephemery.js"; export { mainnetChainConfig, gnosisChainConfig, - goerliChainConfig, - ropstenChainConfig, sepoliaChainConfig, holeskyChainConfig, chiadoChainConfig, ephemeryChainConfig, }; -export type NetworkName = "mainnet" | "gnosis" | "goerli" | "ropsten" | "sepolia" | "holesky" | "chiado" | "ephemery"; +export type NetworkName = "mainnet" | "gnosis" | "sepolia" | "holesky" | "chiado" | "ephemery"; export const networksChainConfig: Record = { mainnet: mainnetChainConfig, gnosis: gnosisChainConfig, - goerli: goerliChainConfig, - ropsten: ropstenChainConfig, sepolia: sepoliaChainConfig, holesky: holeskyChainConfig, chiado: chiadoChainConfig, @@ -45,14 +39,6 @@ export const genesisData: Record = { genesisTime: 1638993340, genesisValidatorsRoot: "0xf5dcb5564e829aab27264b9becd5dfaa017085611224cb3036f573368dbb9d47", }, - goerli: { - genesisTime: 1616508000, - genesisValidatorsRoot: "0x043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb", - }, - ropsten: { - genesisTime: 1653922800, - genesisValidatorsRoot: "0x44f1e56283ca88b35c789f7f449e52339bc1fefe3a45913a43a6d16edcd33cf1", - }, sepolia: { genesisTime: 1655733600, genesisValidatorsRoot: "0xd8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078", diff --git a/packages/flare/src/cmds/selfSlashAttester.ts b/packages/flare/src/cmds/selfSlashAttester.ts index 048b62273d77..6c42372f45c7 100644 --- a/packages/flare/src/cmds/selfSlashAttester.ts +++ b/packages/flare/src/cmds/selfSlashAttester.ts @@ -22,7 +22,7 @@ export const selfSlashAttester: CliCommand, describe: "Self slash validators of a provided mnemonic with AttesterSlashing", examples: [ { - command: "self-slash-proposer --network goerli", + command: "self-slash-proposer --network holesky", description: "Self slash validators of a provided mnemonic", }, ], diff --git a/packages/flare/src/cmds/selfSlashProposer.ts b/packages/flare/src/cmds/selfSlashProposer.ts index 583050470c76..0b71d5558af8 100644 --- a/packages/flare/src/cmds/selfSlashProposer.ts +++ b/packages/flare/src/cmds/selfSlashProposer.ts @@ -21,7 +21,7 @@ export const selfSlashProposer: CliCommand, describe: "Self slash validators of a provided mnemonic with ProposerSlashing", examples: [ { - command: "self-slash-proposer --network goerli", + command: "self-slash-proposer --network holesky", description: "Self slash validators of a provided mnemonic", }, ], diff --git a/packages/light-client/test/utils/getGenesisData.ts b/packages/light-client/test/utils/getGenesisData.ts index 8e435eb9d218..b9333878e249 100644 --- a/packages/light-client/test/utils/getGenesisData.ts +++ b/packages/light-client/test/utils/getGenesisData.ts @@ -10,7 +10,7 @@ import {NetworkName} from "@lodestar/config/networks.js"; /* eslint-disable no-console */ -const networksInInfura: NetworkName[] = ["mainnet", "goerli"]; +const networksInInfura: NetworkName[] = ["mainnet" /*"goerli"*/]; async function getGenesisData(): Promise { for (const network of networksInInfura) { diff --git a/packages/prover/src/utils/execution.ts b/packages/prover/src/utils/execution.ts index 8b0fac1f784e..91d5f8d625ff 100644 --- a/packages/prover/src/utils/execution.ts +++ b/packages/prover/src/utils/execution.ts @@ -48,8 +48,6 @@ export async function getELBlock( export function getChainCommon(network: string): Common { switch (network) { case "mainnet": - case "goerli": - case "ropsten": case "sepolia": case "holesky": case "ephemery": diff --git a/packages/validator/src/metrics.ts b/packages/validator/src/metrics.ts index 56d94318d12b..dc7d1a11ffac 100644 --- a/packages/validator/src/metrics.ts +++ b/packages/validator/src/metrics.ts @@ -20,7 +20,7 @@ export type LodestarGitData = { version: string; /** "4f816b16dfde718e2d74f95f2c8292596138c248" */ commit: string; - /** "goerli" */ + /** "holesky" */ network: string; }; diff --git a/packages/validator/test/unit/utils/interopConfigs.ts b/packages/validator/test/unit/utils/interopConfigs.ts index 5d528c511a3d..2c05203660f6 100644 --- a/packages/validator/test/unit/utils/interopConfigs.ts +++ b/packages/validator/test/unit/utils/interopConfigs.ts @@ -1,34 +1,58 @@ /* eslint-disable @typescript-eslint/naming-convention */ -export const lightHouseRopstenConfig = { - CONFIG_NAME: "ropsten", +export const lighthouseHoleskyConfig = { + CONFIG_NAME: "holesky", PRESET_BASE: "mainnet", - TERMINAL_TOTAL_DIFFICULTY: "50000000000000000", + TERMINAL_TOTAL_DIFFICULTY: "0", TERMINAL_BLOCK_HASH: "0x0000000000000000000000000000000000000000000000000000000000000000", TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: "18446744073709551615", SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY: "128", - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "100000", - MIN_GENESIS_TIME: "1653318000", - GENESIS_FORK_VERSION: "0x80000069", - GENESIS_DELAY: "604800", - ALTAIR_FORK_VERSION: "0x80000070", - ALTAIR_FORK_EPOCH: "500", - BELLATRIX_FORK_VERSION: "0x80000071", - BELLATRIX_FORK_EPOCH: "750", + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "16384", + MIN_GENESIS_TIME: "1695902100", + GENESIS_FORK_VERSION: "0x01017000", + GENESIS_DELAY: "300", + ALTAIR_FORK_VERSION: "0x02017000", + ALTAIR_FORK_EPOCH: "0", + BELLATRIX_FORK_VERSION: "0x03017000", + BELLATRIX_FORK_EPOCH: "0", + CAPELLA_FORK_VERSION: "0x04017000", + CAPELLA_FORK_EPOCH: "256", + DENEB_FORK_VERSION: "0x05017000", + DENEB_FORK_EPOCH: "29696", SECONDS_PER_SLOT: "12", SECONDS_PER_ETH1_BLOCK: "14", MIN_VALIDATOR_WITHDRAWABILITY_DELAY: "256", SHARD_COMMITTEE_PERIOD: "256", ETH1_FOLLOW_DISTANCE: "2048", + SUBNETS_PER_NODE: "2", INACTIVITY_SCORE_BIAS: "4", INACTIVITY_SCORE_RECOVERY_RATE: "16", - EJECTION_BALANCE: "16000000000", + EJECTION_BALANCE: "28000000000", MIN_PER_EPOCH_CHURN_LIMIT: "4", + MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: "8", CHURN_LIMIT_QUOTIENT: "65536", PROPOSER_SCORE_BOOST: "40", - DEPOSIT_CHAIN_ID: "3", - DEPOSIT_NETWORK_ID: "3", - DEPOSIT_CONTRACT_ADDRESS: "0x6f22ffbc56eff051aecf839396dd1ed9ad6bba9d", + DEPOSIT_CHAIN_ID: "17000", + DEPOSIT_NETWORK_ID: "17000", + DEPOSIT_CONTRACT_ADDRESS: "0x4242424242424242424242424242424242424242", + GOSSIP_MAX_SIZE: "10485760", + MAX_REQUEST_BLOCKS: "1024", + EPOCHS_PER_SUBNET_SUBSCRIPTION: "256", + MIN_EPOCHS_FOR_BLOCK_REQUESTS: "33024", + MAX_CHUNK_SIZE: "10485760", + TTFB_TIMEOUT: "5", + RESP_TIMEOUT: "10", + ATTESTATION_PROPAGATION_SLOT_RANGE: "32", + MAXIMUM_GOSSIP_CLOCK_DISPARITY_MILLIS: "500", + MESSAGE_DOMAIN_INVALID_SNAPPY: "0x00000000", + MESSAGE_DOMAIN_VALID_SNAPPY: "0x01000000", + ATTESTATION_SUBNET_EXTRA_BITS: "0", + ATTESTATION_SUBNET_PREFIX_BITS: "6", + ATTESTATION_SUBNET_SHUFFLING_PREFIX_BITS: "3", + MAX_REQUEST_BLOCKS_DENEB: "128", + MAX_REQUEST_BLOB_SIDECARS: "768", + MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: "4096", + BLOB_SIDECAR_SUBNET_COUNT: "6", MAX_COMMITTEES_PER_SLOT: "64", TARGET_COMMITTEE_SIZE: "128", MAX_VALIDATORS_PER_COMMITTEE: "2048", @@ -36,6 +60,7 @@ export const lightHouseRopstenConfig = { HYSTERESIS_QUOTIENT: "4", HYSTERESIS_DOWNWARD_MULTIPLIER: "1", HYSTERESIS_UPWARD_MULTIPLIER: "5", + SAFE_SLOTS_TO_UPDATE_JUSTIFIED: "8", MIN_DEPOSIT_AMOUNT: "1000000000", MAX_EFFECTIVE_BALANCE: "32000000000", EFFECTIVE_BALANCE_INCREMENT: "1000000000", @@ -61,44 +86,68 @@ export const lightHouseRopstenConfig = { MAX_ATTESTATIONS: "128", MAX_DEPOSITS: "16", MAX_VOLUNTARY_EXITS: "16", - MAX_BLS_TO_EXECUTION_CHANGES: "16", INACTIVITY_PENALTY_QUOTIENT_ALTAIR: "50331648", MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: "64", PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: "2", SYNC_COMMITTEE_SIZE: "512", EPOCHS_PER_SYNC_COMMITTEE_PERIOD: "256", MIN_SYNC_COMMITTEE_PARTICIPANTS: "1", + INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: "16777216", + MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: "32", + PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: "3", + MAX_BYTES_PER_TRANSACTION: "1073741824", + MAX_TRANSACTIONS_PER_PAYLOAD: "1048576", + BYTES_PER_LOGS_BLOOM: "256", + MAX_EXTRA_DATA_BYTES: "32", + MAX_BLS_TO_EXECUTION_CHANGES: "16", + MAX_WITHDRAWALS_PER_PAYLOAD: "16", + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: "16384", + MAX_BLOBS_PER_BLOCK: "6", + MAX_BLOB_COMMITMENTS_PER_BLOCK: "4096", + FIELD_ELEMENTS_PER_BLOB: "4096", + DOMAIN_RANDAO: "0x02000000", DOMAIN_DEPOSIT: "0x03000000", - DOMAIN_APPLICATION_MASK: "0x00000001", + DOMAIN_SYNC_COMMITTEE: "0x07000000", DOMAIN_BEACON_ATTESTER: "0x01000000", TARGET_AGGREGATORS_PER_COMMITTEE: "16", - DOMAIN_RANDAO: "0x02000000", - DOMAIN_BEACON_PROPOSER: "0x00000000", - DOMAIN_AGGREGATE_AND_PROOF: "0x06000000", - RANDOM_SUBNETS_PER_VALIDATOR: "1", + SYNC_COMMITTEE_SUBNET_COUNT: "4", BLS_WITHDRAWAL_PREFIX: "0x00", + TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE: "16", + DOMAIN_BEACON_PROPOSER: "0x00000000", + DOMAIN_APPLICATION_MASK: "0x00000001", DOMAIN_SELECTION_PROOF: "0x05000000", - EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: "256", + DOMAIN_AGGREGATE_AND_PROOF: "0x06000000", DOMAIN_VOLUNTARY_EXIT: "0x04000000", - DOMAIN_BLS_TO_EXECUTION_CHANGE: "0x0A000000", + DOMAIN_CONTRIBUTION_AND_PROOF: "0x09000000", + DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF: "0x08000000", }; -export const prysmRopstenConfig = { - ALTAIR_FORK_EPOCH: "500", - ALTAIR_FORK_VERSION: "0x80000070", +export const prysmHoleskyConfig = { + ALTAIR_FORK_EPOCH: "0", + ALTAIR_FORK_VERSION: "0x02017000", + ATTESTATION_PROPAGATION_SLOT_RANGE: "32", + ATTESTATION_SUBNET_COUNT: "64", + ATTESTATION_SUBNET_EXTRA_BITS: "0", + ATTESTATION_SUBNET_PREFIX_BITS: "6", BASE_REWARD_FACTOR: "64", - BELLATRIX_FORK_EPOCH: "750", - BELLATRIX_FORK_VERSION: "0x80000071", + BELLATRIX_FORK_EPOCH: "0", + BELLATRIX_FORK_VERSION: "0x03017000", BLS_WITHDRAWAL_PREFIX: "0x00", + CAPELLA_FORK_EPOCH: "256", + CAPELLA_FORK_VERSION: "0x04017000", CHURN_LIMIT_QUOTIENT: "65536", - CONFIG_NAME: "ropsten", - DEPOSIT_CHAIN_ID: "3", - DEPOSIT_CONTRACT_ADDRESS: "0x6f22fFbC56eFF051aECF839396DD1eD9aD6BBA9D", - DEPOSIT_NETWORK_ID: "3", + CONFIG_NAME: "holesky", + DENEB_FORK_EPOCH: "29696", + DENEB_FORK_VERSION: "0x05017000", + DEPOSIT_CHAIN_ID: "17000", + DEPOSIT_CONTRACT_ADDRESS: "0x4242424242424242424242424242424242424242", + DEPOSIT_NETWORK_ID: "17000", DOMAIN_AGGREGATE_AND_PROOF: "0x06000000", + DOMAIN_APPLICATION_BUILDER: "0x00000001", DOMAIN_APPLICATION_MASK: "0x00000001", DOMAIN_BEACON_ATTESTER: "0x01000000", DOMAIN_BEACON_PROPOSER: "0x00000000", + DOMAIN_BLS_TO_EXECUTION_CHANGE: "0x0a000000", DOMAIN_CONTRIBUTION_AND_PROOF: "0x09000000", DOMAIN_DEPOSIT: "0x03000000", DOMAIN_RANDAO: "0x02000000", @@ -106,18 +155,19 @@ export const prysmRopstenConfig = { DOMAIN_SYNC_COMMITTEE: "0x07000000", DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF: "0x08000000", DOMAIN_VOLUNTARY_EXIT: "0x04000000", - DOMAIN_BLS_TO_EXECUTION_CHANGE: "0x0A000000", EFFECTIVE_BALANCE_INCREMENT: "1000000000", - EJECTION_BALANCE: "16000000000", + EJECTION_BALANCE: "28000000000", EPOCHS_PER_ETH1_VOTING_PERIOD: "64", EPOCHS_PER_HISTORICAL_VECTOR: "65536", EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: "256", EPOCHS_PER_SLASHINGS_VECTOR: "8192", + EPOCHS_PER_SUBNET_SUBSCRIPTION: "256", EPOCHS_PER_SYNC_COMMITTEE_PERIOD: "256", ETH1_ADDRESS_WITHDRAWAL_PREFIX: "0x01", ETH1_FOLLOW_DISTANCE: "2048", - GENESIS_DELAY: "604800", - GENESIS_FORK_VERSION: "0x80000069", + GENESIS_DELAY: "300", + GENESIS_FORK_VERSION: "0x01017000", + GOSSIP_MAX_SIZE: "10485760", HISTORICAL_ROOTS_LIMIT: "16777216", HYSTERESIS_DOWNWARD_MULTIPLIER: "1", HYSTERESIS_QUOTIENT: "4", @@ -128,21 +178,34 @@ export const prysmRopstenConfig = { INACTIVITY_SCORE_BIAS: "4", INACTIVITY_SCORE_RECOVERY_RATE: "16", INTERVALS_PER_SLOT: "3", + MAXIMUM_GOSSIP_CLOCK_DISPARITY: "500", MAX_ATTESTATIONS: "128", MAX_ATTESTER_SLASHINGS: "2", + MAX_BLS_TO_EXECUTION_CHANGES: "16", + MAX_CHUNK_SIZE: "10485760", MAX_COMMITTEES_PER_SLOT: "64", MAX_DEPOSITS: "16", MAX_EFFECTIVE_BALANCE: "32000000000", + MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: "8", MAX_PROPOSER_SLASHINGS: "16", + MAX_REQUEST_BLOB_SIDECARS: "768", + MAX_REQUEST_BLOCKS: "1024", + MAX_REQUEST_BLOCKS_DENEB: "128", + MAX_REQUEST_LIGHT_CLIENT_UPDATES: "128", MAX_SEED_LOOKAHEAD: "4", MAX_VALIDATORS_PER_COMMITTEE: "2048", + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: "16384", MAX_VOLUNTARY_EXITS: "16", - MAX_BLS_TO_EXECUTION_CHANGES: "16", + MAX_WITHDRAWALS_PER_PAYLOAD: "16", + MESSAGE_DOMAIN_INVALID_SNAPPY: "0x00000000", + MESSAGE_DOMAIN_VALID_SNAPPY: "0x01000000", MIN_ATTESTATION_INCLUSION_DELAY: "1", MIN_DEPOSIT_AMOUNT: "1000000000", + MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: "4096", + MIN_EPOCHS_FOR_BLOCK_REQUESTS: "33024", MIN_EPOCHS_TO_INACTIVITY_PENALTY: "4", - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "100000", - MIN_GENESIS_TIME: "1653318000", + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "16384", + MIN_GENESIS_TIME: "1695902100", MIN_PER_EPOCH_CHURN_LIMIT: "4", MIN_SEED_LOOKAHEAD: "1", MIN_SLASHING_PENALTY_QUOTIENT: "128", @@ -150,6 +213,7 @@ export const prysmRopstenConfig = { MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: "32", MIN_SYNC_COMMITTEE_PARTICIPANTS: "1", MIN_VALIDATOR_WITHDRAWABILITY_DELAY: "256", + NODE_ID_BITS: "256", PRESET_BASE: "mainnet", PROPORTIONAL_SLASHING_MULTIPLIER: "1", PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: "2", @@ -158,13 +222,17 @@ export const prysmRopstenConfig = { PROPOSER_SCORE_BOOST: "40", PROPOSER_WEIGHT: "8", RANDOM_SUBNETS_PER_VALIDATOR: "1", - SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY: "128", + REORG_MAX_EPOCHS_SINCE_FINALIZATION: "2", + REORG_PARENT_WEIGHT_THRESHOLD: "160", + REORG_WEIGHT_THRESHOLD: "20", + RESP_TIMEOUT: "10", SECONDS_PER_ETH1_BLOCK: "14", SECONDS_PER_SLOT: "12", SHARD_COMMITTEE_PERIOD: "256", SHUFFLE_ROUND_COUNT: "90", SLOTS_PER_EPOCH: "32", SLOTS_PER_HISTORICAL_ROOT: "8192", + SUBNETS_PER_NODE: "2", SYNC_COMMITTEE_SIZE: "512", SYNC_COMMITTEE_SUBNET_COUNT: "4", SYNC_REWARD_WEIGHT: "2", @@ -173,114 +241,151 @@ export const prysmRopstenConfig = { TARGET_COMMITTEE_SIZE: "128", TERMINAL_BLOCK_HASH: "0x0000000000000000000000000000000000000000000000000000000000000000", TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: "18446744073709551615", - TERMINAL_TOTAL_DIFFICULTY: "50000000000000000", + TERMINAL_TOTAL_DIFFICULTY: "0", TIMELY_HEAD_FLAG_INDEX: "0x02", TIMELY_HEAD_WEIGHT: "14", TIMELY_SOURCE_FLAG_INDEX: "0x00", TIMELY_SOURCE_WEIGHT: "14", TIMELY_TARGET_FLAG_INDEX: "0x01", TIMELY_TARGET_WEIGHT: "26", + TTFB_TIMEOUT: "5", VALIDATOR_REGISTRY_LIMIT: "1099511627776", WEIGHT_DENOMINATOR: "64", WHISTLEBLOWER_REWARD_QUOTIENT: "512", }; -export const tekuRopstenConfig = { - DEPOSIT_CONTRACT_ADDRESS: "0x6f22fFbC56eFF051aECF839396DD1eD9aD6BBA9D", - MIN_ATTESTATION_INCLUSION_DELAY: "1", +export const tekuHoleskyConfig = { SLOTS_PER_EPOCH: "32", PRESET_BASE: "mainnet", - TERMINAL_TOTAL_DIFFICULTY: "50000000000000000", + TERMINAL_TOTAL_DIFFICULTY: "0", INACTIVITY_SCORE_BIAS: "4", + MAX_ATTESTER_SLASHINGS: "2", + MAX_WITHDRAWALS_PER_PAYLOAD: "16", + INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: "16777216", + INACTIVITY_PENALTY_QUOTIENT: "67108864", + SAFE_SLOTS_TO_UPDATE_JUSTIFIED: "8", + SECONDS_PER_ETH1_BLOCK: "14", + MIN_SEED_LOOKAHEAD: "1", + VALIDATOR_REGISTRY_LIMIT: "1099511627776", + REORG_MAX_EPOCHS_SINCE_FINALIZATION: "2", + SLOTS_PER_HISTORICAL_ROOT: "8192", + RESP_TIMEOUT: "10", + DOMAIN_VOLUNTARY_EXIT: "0x04000000", + MAX_VALIDATORS_PER_COMMITTEE: "2048", + MIN_GENESIS_TIME: "1695902100", + ALTAIR_FORK_EPOCH: "0", + HYSTERESIS_QUOTIENT: "4", + ALTAIR_FORK_VERSION: "0x02017000", + MAX_BYTES_PER_TRANSACTION: "1073741824", + MAX_CHUNK_SIZE: "10485760", + TTFB_TIMEOUT: "5", + WHISTLEBLOWER_REWARD_QUOTIENT: "512", + PROPOSER_REWARD_QUOTIENT: "8", + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: "16384", + EPOCHS_PER_HISTORICAL_VECTOR: "65536", + MIN_PER_EPOCH_CHURN_LIMIT: "4", + TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE: "16", + MAX_DEPOSITS: "16", + BELLATRIX_FORK_EPOCH: "0", + MAX_REQUEST_BLOB_SIDECARS: "768", + REORG_HEAD_WEIGHT_THRESHOLD: "20", + TARGET_AGGREGATORS_PER_COMMITTEE: "16", + DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF: "0x08000000", + MESSAGE_DOMAIN_INVALID_SNAPPY: "0x00000000", + EPOCHS_PER_SLASHINGS_VECTOR: "8192", + MIN_SLASHING_PENALTY_QUOTIENT: "128", + MAX_BLS_TO_EXECUTION_CHANGES: "16", + GOSSIP_MAX_SIZE: "10485760", + DOMAIN_BEACON_ATTESTER: "0x01000000", + EPOCHS_PER_SUBNET_SUBSCRIPTION: "256", + ATTESTATION_SUBNET_COUNT: "64", + GENESIS_DELAY: "300", + MAX_SEED_LOOKAHEAD: "4", + ETH1_FOLLOW_DISTANCE: "2048", + SECONDS_PER_SLOT: "12", + REORG_PARENT_WEIGHT_THRESHOLD: "160", + MIN_SYNC_COMMITTEE_PARTICIPANTS: "1", + MAX_EXECUTION_LAYER_EXITS: "16", + BELLATRIX_FORK_VERSION: "0x03017000", + PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: "3", + EFFECTIVE_BALANCE_INCREMENT: "1000000000", + FIELD_ELEMENTS_PER_BLOB: "4096", + MIN_EPOCHS_TO_INACTIVITY_PENALTY: "4", + BASE_REWARD_FACTOR: "64", + MAX_EXTRA_DATA_BYTES: "32", + CONFIG_NAME: "holesky", + MAX_PROPOSER_SLASHINGS: "16", + INACTIVITY_SCORE_RECOVERY_RATE: "16", + MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: "4096", + MAX_TRANSACTIONS_PER_PAYLOAD: "1048576", + DEPOSIT_CONTRACT_ADDRESS: "0x4242424242424242424242424242424242424242", + MIN_ATTESTATION_INCLUSION_DELAY: "1", SHUFFLE_ROUND_COUNT: "90", TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: "18446744073709551615", MAX_EFFECTIVE_BALANCE: "32000000000", DOMAIN_BEACON_PROPOSER: "0x00000000", + DENEB_FORK_EPOCH: "29696", DOMAIN_SYNC_COMMITTEE: "0x07000000", PROPOSER_SCORE_BOOST: "40", - MAX_ATTESTER_SLASHINGS: "2", DOMAIN_SELECTION_PROOF: "0x05000000", MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: "32", - INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: "16777216", - INACTIVITY_PENALTY_QUOTIENT: "67108864", - SECONDS_PER_ETH1_BLOCK: "14", + MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: "8", HYSTERESIS_UPWARD_MULTIPLIER: "5", - MIN_SEED_LOOKAHEAD: "1", - VALIDATOR_REGISTRY_LIMIT: "1099511627776", + SUBNETS_PER_NODE: "2", MIN_DEPOSIT_AMOUNT: "1000000000", - SLOTS_PER_HISTORICAL_ROOT: "8192", PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: "2", + MAX_BLOBS_PER_BLOCK: "6", MIN_VALIDATOR_WITHDRAWABILITY_DELAY: "256", - DOMAIN_VOLUNTARY_EXIT: "0x04000000", - DOMAIN_BLS_TO_EXECUTION_CHANGE: "0x0A000000", + MAXIMUM_GOSSIP_CLOCK_DISPARITY: "500", TARGET_COMMITTEE_SIZE: "128", - MAX_VALIDATORS_PER_COMMITTEE: "2048", - MIN_GENESIS_TIME: "1653318000", TERMINAL_BLOCK_HASH: "0x0000000000000000000000000000000000000000000000000000000000000000", - ALTAIR_FORK_EPOCH: "500", - HYSTERESIS_QUOTIENT: "4", - ALTAIR_FORK_VERSION: "0x80000070", - RANDOM_SUBNETS_PER_VALIDATOR: "1", DOMAIN_DEPOSIT: "0x03000000", - MAX_BYTES_PER_TRANSACTION: "1073741824", DOMAIN_CONTRIBUTION_AND_PROOF: "0x09000000", UPDATE_TIMEOUT: "8192", - WHISTLEBLOWER_REWARD_QUOTIENT: "512", - PROPOSER_REWARD_QUOTIENT: "8", - DEPOSIT_CHAIN_ID: "3", + ELECTRA_FORK_EPOCH: "18446744073709551615", + SYNC_COMMITTEE_BRANCH_LENGTH: "5", + DEPOSIT_CHAIN_ID: "17000", + MAX_BLOB_COMMITMENTS_PER_BLOCK: "4096", DOMAIN_RANDAO: "0x02000000", - EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: "256", - EPOCHS_PER_HISTORICAL_VECTOR: "65536", - MIN_PER_EPOCH_CHURN_LIMIT: "4", + CAPELLA_FORK_VERSION: "0x04017000", MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: "64", - TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE: "16", EPOCHS_PER_ETH1_VOTING_PERIOD: "64", - MAX_DEPOSITS: "16", - BELLATRIX_FORK_EPOCH: "750", + MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: "8192", HISTORICAL_ROOTS_LIMIT: "16777216", + ATTESTATION_PROPAGATION_SLOT_RANGE: "32", SYNC_COMMITTEE_SIZE: "512", - TARGET_AGGREGATORS_PER_COMMITTEE: "16", - DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF: "0x08000000", - EPOCHS_PER_SLASHINGS_VECTOR: "8192", - MIN_SLASHING_PENALTY_QUOTIENT: "128", + ATTESTATION_SUBNET_PREFIX_BITS: "6", PROPORTIONAL_SLASHING_MULTIPLIER: "1", + MESSAGE_DOMAIN_VALID_SNAPPY: "0x01000000", MAX_VOLUNTARY_EXITS: "16", - MAX_BLS_TO_EXECUTION_CHANGES: "16", HYSTERESIS_DOWNWARD_MULTIPLIER: "1", - DOMAIN_BEACON_ATTESTER: "0x01000000", DOMAIN_APPLICATION_BUILDER: "0x00000001", EPOCHS_PER_SYNC_COMMITTEE_PERIOD: "256", - GENESIS_DELAY: "604800", - MAX_SEED_LOOKAHEAD: "4", BYTES_PER_LOGS_BLOOM: "256", - ETH1_FOLLOW_DISTANCE: "2048", - SECONDS_PER_SLOT: "12", - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "100000", - MIN_SYNC_COMMITTEE_PARTICIPANTS: "1", + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "16384", MAX_ATTESTATIONS: "128", - BELLATRIX_FORK_VERSION: "0x80000071", - GENESIS_FORK_VERSION: "0x80000069", - DEPOSIT_NETWORK_ID: "3", - PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: "3", + MIN_EPOCHS_FOR_BLOCK_REQUESTS: "33024", + DENEB_FORK_VERSION: "0x05017000", + ELECTRA_FORK_VERSION: "0x06017000", + MAX_REQUEST_BLOCKS: "1024", + GENESIS_FORK_VERSION: "0x01017000", + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: "17", + DEPOSIT_NETWORK_ID: "17000", + MAX_REQUEST_BLOCKS_DENEB: "128", + BLOB_SIDECAR_SUBNET_COUNT: "6", SYNC_COMMITTEE_SUBNET_COUNT: "4", - EJECTION_BALANCE: "16000000000", + CAPELLA_FORK_EPOCH: "256", + EJECTION_BALANCE: "28000000000", + ATTESTATION_SUBNET_EXTRA_BITS: "0", MAX_COMMITTEES_PER_SLOT: "64", SHARD_COMMITTEE_PERIOD: "256", - EFFECTIVE_BALANCE_INCREMENT: "1000000000", INACTIVITY_PENALTY_QUOTIENT_ALTAIR: "50331648", DOMAIN_AGGREGATE_AND_PROOF: "0x06000000", - MIN_EPOCHS_TO_INACTIVITY_PENALTY: "4", - BASE_REWARD_FACTOR: "64", - MAX_EXTRA_DATA_BYTES: "32", - CONFIG_NAME: "ropsten", - MAX_PROPOSER_SLASHINGS: "16", CHURN_LIMIT_QUOTIENT: "65536", - INACTIVITY_SCORE_RECOVERY_RATE: "16", BLS_WITHDRAWAL_PREFIX: "0x00", - MAX_TRANSACTIONS_PER_PAYLOAD: "1048576", }; -export const nimbusRopstenConfig = { +export const nimbusHoleskyConfig = { MAX_COMMITTEES_PER_SLOT: "64", TARGET_COMMITTEE_SIZE: "128", MAX_VALIDATORS_PER_COMMITTEE: "2048", @@ -313,7 +418,6 @@ export const nimbusRopstenConfig = { MAX_ATTESTATIONS: "128", MAX_DEPOSITS: "16", MAX_VOLUNTARY_EXITS: "16", - MAX_BLS_TO_EXECUTION_CHANGES: "16", INACTIVITY_PENALTY_QUOTIENT_ALTAIR: "50331648", MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: "64", PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: "2", @@ -328,19 +432,30 @@ export const nimbusRopstenConfig = { MAX_TRANSACTIONS_PER_PAYLOAD: "1048576", BYTES_PER_LOGS_BLOOM: "256", MAX_EXTRA_DATA_BYTES: "32", + MAX_BLS_TO_EXECUTION_CHANGES: "16", + MAX_WITHDRAWALS_PER_PAYLOAD: "16", + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: "16384", + FIELD_ELEMENTS_PER_BLOB: "4096", + MAX_BLOB_COMMITMENTS_PER_BLOCK: "4096", + MAX_BLOBS_PER_BLOCK: "6", + KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: "17", PRESET_BASE: "mainnet", - CONFIG_NAME: "ropsten", - TERMINAL_TOTAL_DIFFICULTY: "50000000000000000", + CONFIG_NAME: "holesky", + TERMINAL_TOTAL_DIFFICULTY: "0", TERMINAL_BLOCK_HASH: "0x0000000000000000000000000000000000000000000000000000000000000000", TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: "18446744073709551615", - MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "100000", - MIN_GENESIS_TIME: "1653318000", - GENESIS_FORK_VERSION: "0x80000069", - GENESIS_DELAY: "604800", - ALTAIR_FORK_VERSION: "0x80000070", - ALTAIR_FORK_EPOCH: "500", - BELLATRIX_FORK_VERSION: "0x80000071", - BELLATRIX_FORK_EPOCH: "750", + MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: "16384", + MIN_GENESIS_TIME: "1695902100", + GENESIS_FORK_VERSION: "0x01017000", + GENESIS_DELAY: "300", + ALTAIR_FORK_VERSION: "0x02017000", + ALTAIR_FORK_EPOCH: "0", + BELLATRIX_FORK_VERSION: "0x03017000", + BELLATRIX_FORK_EPOCH: "0", + CAPELLA_FORK_VERSION: "0x04017000", + CAPELLA_FORK_EPOCH: "256", + DENEB_FORK_VERSION: "0x05017000", + DENEB_FORK_EPOCH: "29696", SECONDS_PER_SLOT: "12", SECONDS_PER_ETH1_BLOCK: "14", MIN_VALIDATOR_WITHDRAWABILITY_DELAY: "256", @@ -348,13 +463,36 @@ export const nimbusRopstenConfig = { ETH1_FOLLOW_DISTANCE: "2048", INACTIVITY_SCORE_BIAS: "4", INACTIVITY_SCORE_RECOVERY_RATE: "16", - EJECTION_BALANCE: "16000000000", + EJECTION_BALANCE: "28000000000", MIN_PER_EPOCH_CHURN_LIMIT: "4", CHURN_LIMIT_QUOTIENT: "65536", + MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: "8", PROPOSER_SCORE_BOOST: "40", - DEPOSIT_CHAIN_ID: "3", - DEPOSIT_NETWORK_ID: "3", - DEPOSIT_CONTRACT_ADDRESS: "0x6f22ffbc56eff051aecf839396dd1ed9ad6bba9d", + REORG_HEAD_WEIGHT_THRESHOLD: "20", + REORG_PARENT_WEIGHT_THRESHOLD: "160", + REORG_MAX_EPOCHS_SINCE_FINALIZATION: "2", + DEPOSIT_CHAIN_ID: "17000", + DEPOSIT_NETWORK_ID: "17000", + DEPOSIT_CONTRACT_ADDRESS: "0x4242424242424242424242424242424242424242", + GOSSIP_MAX_SIZE: "10485760", + MAX_REQUEST_BLOCKS: "1024", + EPOCHS_PER_SUBNET_SUBSCRIPTION: "256", + MIN_EPOCHS_FOR_BLOCK_REQUESTS: "33024", + MAX_CHUNK_SIZE: "10485760", + TTFB_TIMEOUT: "5", + RESP_TIMEOUT: "10", + ATTESTATION_PROPAGATION_SLOT_RANGE: "32", + MAXIMUM_GOSSIP_CLOCK_DISPARITY: "500", + MESSAGE_DOMAIN_INVALID_SNAPPY: "0x00000000", + MESSAGE_DOMAIN_VALID_SNAPPY: "0x01000000", + SUBNETS_PER_NODE: "2", + ATTESTATION_SUBNET_COUNT: "64", + ATTESTATION_SUBNET_EXTRA_BITS: "0", + ATTESTATION_SUBNET_PREFIX_BITS: "6", + MAX_REQUEST_BLOCKS_DENEB: "128", + MAX_REQUEST_BLOB_SIDECARS: "768", + MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: "4096", + BLOB_SIDECAR_SUBNET_COUNT: "6", BLS_WITHDRAWAL_PREFIX: "0x00", ETH1_ADDRESS_WITHDRAWAL_PREFIX: "0x01", DOMAIN_BEACON_PROPOSER: "0x00000000", @@ -362,12 +500,11 @@ export const nimbusRopstenConfig = { DOMAIN_RANDAO: "0x02000000", DOMAIN_DEPOSIT: "0x03000000", DOMAIN_VOLUNTARY_EXIT: "0x04000000", - DOMAIN_BLS_TO_EXECUTION_CHANGE: "0x0A000000", DOMAIN_SELECTION_PROOF: "0x05000000", DOMAIN_AGGREGATE_AND_PROOF: "0x06000000", - TIMELY_SOURCE_FLAG_INDEX: "0x00", - TIMELY_TARGET_FLAG_INDEX: "0x01", - TIMELY_HEAD_FLAG_INDEX: "0x02", + TIMELY_SOURCE_FLAG_INDEX: "0", + TIMELY_TARGET_FLAG_INDEX: "1", + TIMELY_HEAD_FLAG_INDEX: "2", TIMELY_SOURCE_WEIGHT: "14", TIMELY_TARGET_WEIGHT: "26", TIMELY_HEAD_WEIGHT: "14", @@ -377,10 +514,8 @@ export const nimbusRopstenConfig = { DOMAIN_SYNC_COMMITTEE: "0x07000000", DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF: "0x08000000", DOMAIN_CONTRIBUTION_AND_PROOF: "0x09000000", + DOMAIN_BLS_TO_EXECUTION_CHANGE: "0x0a000000", TARGET_AGGREGATORS_PER_COMMITTEE: "16", - RANDOM_SUBNETS_PER_VALIDATOR: "1", - EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: "256", - ATTESTATION_SUBNET_COUNT: "64", TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE: "16", SYNC_COMMITTEE_SUBNET_COUNT: "4", }; diff --git a/packages/validator/test/unit/utils/params.test.ts b/packages/validator/test/unit/utils/params.test.ts index 47b743e0ce5e..79701e1f7a6e 100644 --- a/packages/validator/test/unit/utils/params.test.ts +++ b/packages/validator/test/unit/utils/params.test.ts @@ -3,13 +3,13 @@ import {chainConfigToJson, ChainConfig} from "@lodestar/config"; import {chainConfig} from "@lodestar/config/default"; import {networksChainConfig} from "@lodestar/config/networks"; import {assertEqualParams, NotEqualParamsError} from "../../../src/util/params.js"; -import {lightHouseRopstenConfig, prysmRopstenConfig, tekuRopstenConfig, nimbusRopstenConfig} from "./interopConfigs.js"; +import {lighthouseHoleskyConfig, prysmHoleskyConfig, tekuHoleskyConfig, nimbusHoleskyConfig} from "./interopConfigs.js"; const testCases: {name: string; items: [ChainConfig, Record]}[] = [ - {name: "lighthouse", items: [networksChainConfig.ropsten, lightHouseRopstenConfig]}, - {name: "prysm", items: [networksChainConfig.ropsten, prysmRopstenConfig]}, - {name: "teku", items: [networksChainConfig.ropsten, tekuRopstenConfig]}, - {name: "nimbus", items: [networksChainConfig.ropsten, nimbusRopstenConfig]}, + {name: "lighthouse", items: [networksChainConfig.holesky, lighthouseHoleskyConfig]}, + {name: "prysm", items: [networksChainConfig.holesky, prysmHoleskyConfig]}, + {name: "teku", items: [networksChainConfig.holesky, tekuHoleskyConfig]}, + {name: "nimbus", items: [networksChainConfig.holesky, nimbusHoleskyConfig]}, ]; /* eslint-disable @typescript-eslint/naming-convention */ @@ -38,7 +38,7 @@ describe("utils / params / assertEqualParams", () => { }); for (const {name, items} of testCases) { - it(`${name} ropsten == lodestar ropsten`, () => { + it(`${name} holesky == lodestar holesky`, () => { assertEqualParams(items[0], items[1]); }); } From ff253a7e4b3dd0728e12c3beee7af702beb53c37 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 10 Jun 2024 16:27:17 +0200 Subject: [PATCH 12/55] refactor(test): use the configurable logger consistently via top level interface of crucible (#6871) * Use the logger consistently * Fix types * Fix lint errors * Fix types --- .../crucible/clients/beacon/lighthouse.ts | 5 +- .../utils/crucible/clients/beacon/lodestar.ts | 7 +- .../utils/crucible/clients/execution/geth.ts | 7 +- .../crucible/clients/execution/nethermind.ts | 7 +- .../crucible/clients/validator/lighthouse.ts | 5 +- .../crucible/clients/validator/lodestar.ts | 7 +- .../cli/test/utils/crucible/interfaces.ts | 7 +- .../crucible/runner/childProcessRunner.ts | 23 +- .../utils/crucible/runner/dockerRunner.ts | 32 +- .../cli/test/utils/crucible/runner/index.ts | 15 +- .../cli/test/utils/crucible/simulation.ts | 7 +- packages/test-utils/src/childProcess.ts | 311 ++++++++++-------- 12 files changed, 206 insertions(+), 227 deletions(-) diff --git a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts index 500b93ee2fb7..89802149024a 100644 --- a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts +++ b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts @@ -92,12 +92,11 @@ export const generateLighthouseBeaconNode: BeaconNodeGenerator { try { await got.get(`http://127.0.0.1:${ports.beacon.httpPort}/eth/v1/node/health`); - return {ok: true}; } catch (err) { if (err instanceof RequestError && err.code !== "ECONNREFUSED") { - return {ok: true}; + return; } - return {ok: false, reason: (err as Error).message, checkId: "/eth/v1/node/health query"}; + throw err; } }, }, diff --git a/packages/cli/test/utils/crucible/clients/beacon/lodestar.ts b/packages/cli/test/utils/crucible/clients/beacon/lodestar.ts index 0c881b5b3402..a91218924b2f 100644 --- a/packages/cli/test/utils/crucible/clients/beacon/lodestar.ts +++ b/packages/cli/test/utils/crucible/clients/beacon/lodestar.ts @@ -95,12 +95,7 @@ export const generateLodestarBeaconNode: BeaconNodeGenerator { - try { - await got.get(`http://${address}:${ports.beacon.httpPort}/eth/v1/node/health`); - return {ok: true}; - } catch (err) { - return {ok: false, reason: (err as Error).message, checkId: "eth/v1/node/health query"}; - } + await got.get(`http://${address}:${ports.beacon.httpPort}/eth/v1/node/health`); }, }, ]); diff --git a/packages/cli/test/utils/crucible/clients/execution/geth.ts b/packages/cli/test/utils/crucible/clients/execution/geth.ts index 7cd24a90da04..c8e7bc7c38da 100644 --- a/packages/cli/test/utils/crucible/clients/execution/geth.ts +++ b/packages/cli/test/utils/crucible/clients/execution/geth.ts @@ -154,12 +154,7 @@ export const generateGethNode: ExecutionNodeGenerator = (o stdoutFilePath: logFilePath, }, health: async () => { - try { - await got.post(ethRpcPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); - return {ok: true}; - } catch (err) { - return {ok: false, reason: (err as Error).message, checkId: "JSON RPC query net_version"}; - } + await got.post(ethRpcPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); }, }; diff --git a/packages/cli/test/utils/crucible/clients/execution/nethermind.ts b/packages/cli/test/utils/crucible/clients/execution/nethermind.ts index 53f190f3ec76..1c991afa6189 100644 --- a/packages/cli/test/utils/crucible/clients/execution/nethermind.ts +++ b/packages/cli/test/utils/crucible/clients/execution/nethermind.ts @@ -118,12 +118,7 @@ export const generateNethermindNode: ExecutionNodeGenerator { - try { - await got.post(ethRpcPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); - return {ok: true}; - } catch (err) { - return {ok: false, reason: (err as Error).message, checkId: "JSON RPC query net_version"}; - } + await got.post(ethRpcPublicUrl, {json: {jsonrpc: "2.0", method: "net_version", params: [], id: 67}}); }, }; diff --git a/packages/cli/test/utils/crucible/clients/validator/lighthouse.ts b/packages/cli/test/utils/crucible/clients/validator/lighthouse.ts index b9c70c495ce6..f99310dc7907 100644 --- a/packages/cli/test/utils/crucible/clients/validator/lighthouse.ts +++ b/packages/cli/test/utils/crucible/clients/validator/lighthouse.ts @@ -83,12 +83,11 @@ export const generateLighthouseValidatorNode: ValidatorNodeGenerator { try { await got.get(`http://127.0.0.1:${ports.validator.keymanagerPort}/lighthouse/health`); - return {ok: true}; } catch (err) { if (err instanceof RequestError) { - return {ok: true}; + return; } - return {ok: false, reason: (err as Error).message, checkId: "/lighthouse/health query"}; + throw err; } }, }, diff --git a/packages/cli/test/utils/crucible/clients/validator/lodestar.ts b/packages/cli/test/utils/crucible/clients/validator/lodestar.ts index df91c92e2b76..e95c39972eb0 100644 --- a/packages/cli/test/utils/crucible/clients/validator/lodestar.ts +++ b/packages/cli/test/utils/crucible/clients/validator/lodestar.ts @@ -69,12 +69,7 @@ export const generateLodestarValidatorNode: ValidatorNodeGenerator { - try { - await got.get(`http://127.0.0.1:${ports.validator.keymanagerPort}/eth/v1/keystores`); - return {ok: true}; - } catch (err) { - return {ok: false, reason: (err as Error).message, checkId: "eth/v1/keystores query"}; - } + await got.get(`http://127.0.0.1:${ports.validator.keymanagerPort}/eth/v1/keystores`); }, }, ]); diff --git a/packages/cli/test/utils/crucible/interfaces.ts b/packages/cli/test/utils/crucible/interfaces.ts index 44537369f2b4..c406d5a72432 100644 --- a/packages/cli/test/utils/crucible/interfaces.ts +++ b/packages/cli/test/utils/crucible/interfaces.ts @@ -7,7 +7,7 @@ import {ApiClient as KeyManagerApi} from "@lodestar/api/keymanager"; import {ChainForkConfig} from "@lodestar/config"; import {ForkName} from "@lodestar/params"; import {Slot, allForks, Epoch} from "@lodestar/types"; -import {Logger} from "@lodestar/logger"; +import {LogLevel, Logger} from "@lodestar/logger"; import {BeaconArgs} from "../../../src/cmds/beacon/options.js"; import {IValidatorCliArgs} from "../../../src/cmds/validator/options.js"; import {GlobalArgs} from "../../../src/options/index.js"; @@ -29,6 +29,7 @@ export type SimulationOptions = { controller: AbortController; genesisTime: number; trustedSetup?: boolean; + logLevel?: LogLevel; }; export enum BeaconClient { @@ -234,8 +235,6 @@ export type ExecutionNodeGenerator = ( runner: IRunner ) => ExecutionNode; -export type HealthStatus = {ok: true} | {ok: false; reason: string; checkId: string}; - export type JobOptions = { readonly id: string; @@ -257,7 +256,7 @@ export type JobOptions; + health?(): Promise; // Called once before the `job.start` is called bootstrap?(): Promise; diff --git a/packages/cli/test/utils/crucible/runner/childProcessRunner.ts b/packages/cli/test/utils/crucible/runner/childProcessRunner.ts index 8554a2d9a3be..4c2041d0860f 100644 --- a/packages/cli/test/utils/crucible/runner/childProcessRunner.ts +++ b/packages/cli/test/utils/crucible/runner/childProcessRunner.ts @@ -1,15 +1,15 @@ import {ChildProcess} from "node:child_process"; -import { - spawnChildProcess, - stopChildProcess, - ChildProcessHealthStatus, - SpawnChildProcessOptions, - ChildProcessResolve, -} from "@lodestar/test-utils"; +import {spawnChildProcess, stopChildProcess, SpawnChildProcessOptions, ChildProcessResolve} from "@lodestar/test-utils"; +import {Logger} from "@lodestar/logger"; import {Job, JobOptions, RunnerEnv, RunnerType} from "../interfaces.js"; export class ChildProcessRunner implements RunnerEnv { type = RunnerType.ChildProcess as const; + private logger: Logger; + + constructor(opts: {logger: Logger}) { + this.logger = opts.logger; + } create(jobOption: Omit, "children">): Job { let childProcess: ChildProcess; @@ -24,14 +24,7 @@ export class ChildProcessRunner implements RunnerEnv { if (health) { spawnOpts.healthTimeoutMs = 30000; - spawnOpts.health = async (): Promise => - health() - .then((status) => { - return status.ok ? {healthy: true} : {healthy: false}; - }) - .catch((error) => { - return {healthy: false, message: (error as Error).message}; - }); + spawnOpts.health = health; } else { spawnOpts.resolveOn = ChildProcessResolve.Completion; } diff --git a/packages/cli/test/utils/crucible/runner/dockerRunner.ts b/packages/cli/test/utils/crucible/runner/dockerRunner.ts index 745335124ab8..cfa52c9b9ebf 100644 --- a/packages/cli/test/utils/crucible/runner/dockerRunner.ts +++ b/packages/cli/test/utils/crucible/runner/dockerRunner.ts @@ -1,12 +1,7 @@ import {ChildProcess} from "node:child_process"; +import {Logger} from "@lodestar/logger"; import {sleep} from "@lodestar/utils"; -import { - ChildProcessHealthStatus, - SpawnChildProcessOptions, - execChildProcess, - spawnChildProcess, - ChildProcessResolve, -} from "@lodestar/test-utils"; +import {SpawnChildProcessOptions, execChildProcess, spawnChildProcess, ChildProcessResolve} from "@lodestar/test-utils"; import {Job, JobOptions, RunnerEnv, RunnerType} from "../interfaces.js"; const dockerNetworkIpRange = "192.168.0"; @@ -14,19 +9,18 @@ const dockerNetworkName = "sim-env-net"; export class DockerRunner implements RunnerEnv { type = RunnerType.Docker as const; + private logger: Logger; private ipIndex = 2; - private logFilePath: string; - constructor(logFilePath: string) { - this.logFilePath = logFilePath; + constructor(opts: {logger: Logger}) { + this.logger = opts.logger; } async start(): Promise { try { await execChildProcess(`docker network create --subnet ${dockerNetworkIpRange}.0/24 ${dockerNetworkName}`, { - logPrefix: "docker-runner", - pipeStdioToFile: this.logFilePath, + logger: this.logger, }); } catch { // During multiple sim tests files the network might already exist @@ -38,8 +32,7 @@ export class DockerRunner implements RunnerEnv { for (let i = 0; i < 5; i++) { try { await execChildProcess(`docker network rm ${dockerNetworkName}`, { - logPrefix: "docker-runner", - pipeStdioToFile: this.logFilePath, + logger: this.logger, }); return; } catch { @@ -97,14 +90,7 @@ export class DockerRunner implements RunnerEnv { if (health) { spawnOpts.healthTimeoutMs = 30000; - spawnOpts.health = async (): Promise => - health() - .then((status) => { - return status.ok ? {healthy: true} : {healthy: false}; - }) - .catch((error) => { - return {healthy: false, message: (error as Error).message}; - }); + spawnOpts.health = health; } else { spawnOpts.resolveOn = ChildProcessResolve.Completion; } @@ -120,7 +106,7 @@ export class DockerRunner implements RunnerEnv { } // TODO: Debug why stopping the process was not killing the container // await stopChildProcess(childProcess); - await execChildProcess(`docker stop ${jobOption.id} --time 2 || true`, {pipeStdioToParent: true}); + await execChildProcess(`docker stop ${jobOption.id} --time 2 || true`, {logger: this.logger}); }, }; } diff --git a/packages/cli/test/utils/crucible/runner/index.ts b/packages/cli/test/utils/crucible/runner/index.ts index 7bc7a2872f34..0330f153b0c4 100644 --- a/packages/cli/test/utils/crucible/runner/index.ts +++ b/packages/cli/test/utils/crucible/runner/index.ts @@ -1,20 +1,19 @@ import {EventEmitter} from "node:events"; -import path from "node:path"; -import {Logger} from "@lodestar/logger"; +import {LoggerNode} from "@lodestar/logger/node"; import {IRunner, Job, JobOptions, RunnerEvent, RunnerType} from "../interfaces.js"; import {ChildProcessRunner} from "./childProcessRunner.js"; import {DockerRunner} from "./dockerRunner.js"; export class Runner implements IRunner { - readonly logger: Logger; + readonly logger: LoggerNode; private emitter = new EventEmitter({captureRejections: true}); private runners: {[RunnerType.ChildProcess]: ChildProcessRunner; [RunnerType.Docker]: DockerRunner}; - constructor({logsDir, logger}: {logsDir: string; logger: Logger}) { + constructor({logger}: {logger: LoggerNode}) { this.logger = logger; this.runners = { - [RunnerType.ChildProcess]: new ChildProcessRunner(), - [RunnerType.Docker]: new DockerRunner(path.join(logsDir, "docker_runner.log")), + [RunnerType.ChildProcess]: new ChildProcessRunner({logger: this.logger.child({module: "cp"})}), + [RunnerType.Docker]: new DockerRunner({logger: this.logger.child({module: "docker"})}), }; } @@ -59,7 +58,7 @@ export class Runner implements IRunner { await job.start(); this.emitter.emit("started", jobOption.id); - this.logger.info(`Started "${jobOption.id}" logFile=${jobOption.logs.stdoutFilePath}...`); + this.logger.debug(`Started "${jobOption.id}" logFile=${jobOption.logs.stdoutFilePath}...`); if (childrenJob) await childrenJob.start(); }); @@ -71,7 +70,7 @@ export class Runner implements IRunner { if (childrenJob) await childrenJob.stop(); await job.stop(); this.emitter.emit("stopped", jobOption.id); - this.logger.info(`Stopped "${jobOption.id}"...`); + this.logger.debug(`Stopped "${jobOption.id}"...`); }); } diff --git a/packages/cli/test/utils/crucible/simulation.ts b/packages/cli/test/utils/crucible/simulation.ts index b55ebf110c89..c6e58095474a 100644 --- a/packages/cli/test/utils/crucible/simulation.ts +++ b/packages/cli/test/utils/crucible/simulation.ts @@ -64,7 +64,10 @@ export class Simulation { timestampFormat: { format: TimestampFormatCode.DateRegular, }, - file: {level: LogLevel.debug, filepath: path.join(options.logsDir, `simulation-${this.options.id}.log`)}, + file: { + level: options.logLevel ?? LogLevel.debug, + filepath: path.join(options.logsDir, `simulation-${this.options.id}.log`), + }, }); this.clock = new EpochClock({ genesisTime: this.options.genesisTime + this.forkConfig.GENESIS_DELAY, @@ -74,7 +77,7 @@ export class Simulation { }); this.externalSigner = new ExternalSignerServer([]); - this.runner = new Runner({logsDir: this.options.logsDir, logger: this.logger.child({module: "runner"})}); + this.runner = new Runner({logger: this.logger}); this.tracker = SimulationTracker.initWithDefaults({ logsDir: options.logsDir, logger: this.logger, diff --git a/packages/test-utils/src/childProcess.ts b/packages/test-utils/src/childProcess.ts index ae01d7ce0e86..bc164d0f985d 100644 --- a/packages/test-utils/src/childProcess.ts +++ b/packages/test-utils/src/childProcess.ts @@ -1,11 +1,35 @@ /* eslint-disable no-console */ -import childProcess from "node:child_process"; +import childProcess, {ChildProcess, ChildProcessWithoutNullStreams} from "node:child_process"; import stream from "node:stream"; import fs from "node:fs"; import path from "node:path"; -import {prettyMsToTime, sleep} from "@lodestar/utils"; +import {prettyMsToTime, retry, sleep, Logger} from "@lodestar/utils"; import {TestContext} from "./interfaces.js"; +export type ChildProcessLogOptions = { + /** + * A string key to identify the process in logs + */ + logPrefix?: string; + /** + * Hide stdio from parent process and only show errors + */ + pipeOnlyError?: boolean; +} & ( + | { + /** + * If true, pipe child process stdio to parent process + */ + pipeStdioToFile?: string; + /** + * If true, pipe child process stdio to parent process + */ + pipeStdioToParent?: boolean; + logger?: never; + } + | {logger?: Logger; pipeStdioToFile?: never; pipeStdioToParent?: never; logPrefix?: never} +); + /** * If timeout is greater than 0, the parent will send the signal * identified by the killSignal property (the default is 'SIGTERM') @@ -13,11 +37,8 @@ import {TestContext} from "./interfaces.js"; */ const defaultTimeout = 15 * 60 * 1000; // ms -export type ExecChildProcessOptions = { +export type ExecChildProcessOptions = ChildProcessLogOptions & { env?: Record; - pipeStdioToFile?: string; - pipeStdioToParent?: boolean; - logPrefix?: string; timeoutMs?: number; maxBuffer?: number; signal?: AbortSignal; @@ -28,7 +49,7 @@ export type ExecChildProcessOptions = { * If the child process exits with code > 0, rejects */ export async function execChildProcess(cmd: string | string[], options?: ExecChildProcessOptions): Promise { - const {timeoutMs, maxBuffer, logPrefix, pipeStdioToParent, pipeStdioToFile} = options ?? {}; + const {timeoutMs, maxBuffer} = options ?? {}; const cmdStr = Array.isArray(cmd) ? cmd.join(" ") : cmd; return new Promise((resolve, reject) => { @@ -44,28 +65,7 @@ export async function execChildProcess(cmd: string | string[], options?: ExecChi } ); - const logPrefixStream = new stream.Transform({ - transform(chunk, _encoding, callback) { - callback(null, `${logPrefix} ${proc.pid}: ${Buffer.from(chunk).toString("utf8")}`); - }, - }); - - if (pipeStdioToParent) { - proc.stdout?.pipe(logPrefixStream).pipe(process.stdout); - proc.stderr?.pipe(logPrefixStream).pipe(process.stderr); - } - - if (pipeStdioToFile) { - fs.mkdirSync(path.dirname(pipeStdioToFile), {recursive: true}); - const stdoutFileStream = fs.createWriteStream(pipeStdioToFile); - - proc.stdout?.pipe(logPrefixStream).pipe(stdoutFileStream); - proc.stderr?.pipe(logPrefixStream).pipe(stdoutFileStream); - - proc.once("exit", (_code: number) => { - stdoutFileStream.close(); - }); - } + handleLoggingForChildProcess(proc, options ?? {}); if (options?.signal) { options.signal.addEventListener( @@ -161,33 +161,11 @@ export enum ChildProcessResolve { Healthy, } -export type ChildProcessHealthStatus = {healthy: boolean; error?: string}; - -export type SpawnChildProcessOptions = { - /** - * Environment variables to pass to child process - */ - env?: Record; - /** - * If true, pipe child process stdio to parent process - */ - pipeStdioToFile?: string; - /** - * If true, pipe child process stdio to parent process - */ - pipeStdioToParent?: boolean; +export type HealthCheckOptions = { /** - * The prefix to add to child process stdio to identify it from logs - */ - logPrefix?: string; - /** - * Hide stdio from parent process and only show errors - */ - pipeOnlyError?: boolean; - /** - * Child process resolve behavior + * If health attribute defined we will consider resolveOn = ChildProcessResolve.Healthy */ - resolveOn?: ChildProcessResolve; + health: () => Promise; /** * Timeout to wait for child process before considering it unhealthy */ @@ -200,30 +178,71 @@ export type SpawnChildProcessOptions = { * Log health checks after this time */ logHealthChecksAfterMs?: number; - /** - * Test context to pass to child process. Useful for testing to close the process after test case - */ - testContext?: TestContext; - /** - * Abort signal to stop child process - */ - signal?: AbortSignal; - /** - * If health attribute defined we will consider resolveOn = ChildProcessResolve.Healthy - */ - health?: () => Promise<{healthy: boolean; error?: string}>; }; -const defaultStartOpts = { - env: {}, - pipeStdToParent: false, - pipeOnlyError: false, - logPrefix: "", +export type SpawnChildProcessOptions = Partial & + ChildProcessLogOptions & { + /** + * Environment variables to pass to child process + */ + env?: Record; + /** + * Child process resolve behavior + */ + resolveOn?: ChildProcessResolve; + /** + * Test context to pass to child process. Useful for testing to close the process after test case + */ + testContext?: TestContext; + /** + * Abort signal to stop child process + */ + signal?: AbortSignal; + }; + +const defaultHealthOptions = { healthCheckIntervalMs: 1000, logHealthChecksAfterMs: 2000, + healthTimeoutMs: 10000, +}; + +const defaultStartOpts = { + ...defaultHealthOptions, + env: {}, resolveOn: ChildProcessResolve.Immediate, }; +export async function waitForHealth({ + id, + health, + healthTimeoutMs = defaultHealthOptions.healthTimeoutMs, + logHealthChecksAfterMs = defaultHealthOptions.logHealthChecksAfterMs, + healthCheckIntervalMs = defaultHealthOptions.healthCheckIntervalMs, +}: HealthCheckOptions & {id: string}): Promise { + console.log({healthTimeoutMs, logHealthChecksAfterMs, healthCheckIntervalMs}); + const startHealthCheckMs = Date.now(); + + await retry( + async () => { + try { + await health(); + } catch (error) { + const timeSinceHealthCheckStart = Date.now() - startHealthCheckMs; + if (timeSinceHealthCheckStart > logHealthChecksAfterMs) { + console.log( + `Health check unsuccessful. id=${id} timeSinceHealthCheckStart=${prettyMsToTime(timeSinceHealthCheckStart)}` + ); + } + throw error; + } + }, + { + retryDelay: healthCheckIntervalMs, + retries: healthTimeoutMs === undefined ? 1 : Math.floor(healthTimeoutMs / healthCheckIntervalMs), + } + ); +} + /** * Spawn child process and return it * @@ -237,9 +256,9 @@ export async function spawnChildProcess( args: string[], opts?: Partial ): Promise { - const options = {...defaultStartOpts, ...opts}; - const {env, pipeStdioToFile, pipeStdioToParent, logPrefix, pipeOnlyError, signal} = options; - const {health, resolveOn, healthCheckIntervalMs, logHealthChecksAfterMs, healthTimeoutMs, testContext} = options; + const options = {...defaultStartOpts, ...opts} as SpawnChildProcessOptions; + const {env, signal, health, resolveOn, healthCheckIntervalMs, logHealthChecksAfterMs, healthTimeoutMs} = options; + const {logPrefix, testContext} = options; return new Promise((resolve, reject) => { void (async () => { @@ -247,12 +266,7 @@ export async function spawnChildProcess( env: {...process.env, ...env}, }); - const getLogPrefixStream = (): stream.Transform => - new stream.Transform({ - transform(chunk, _encoding, callback) { - callback(null, `[${logPrefix}] [${proc.pid}]: ${Buffer.from(chunk).toString("utf8")}`); - }, - }); + handleLoggingForChildProcess(proc, options); if (testContext) { testContext.afterEach(async () => { @@ -272,28 +286,6 @@ export async function spawnChildProcess( ); } - if (pipeStdioToFile) { - fs.mkdirSync(path.dirname(pipeStdioToFile), {recursive: true}); - const stdoutFileStream = fs.createWriteStream(pipeStdioToFile); - - proc.stdout.pipe(getLogPrefixStream()).pipe(stdoutFileStream); - proc.stderr.pipe(getLogPrefixStream()).pipe(stdoutFileStream); - - proc.once("exit", (_code: number) => { - stdoutFileStream.close(); - }); - } - - if (pipeStdioToParent) { - proc.stdout.pipe(getLogPrefixStream()).pipe(process.stdout); - proc.stderr.pipe(getLogPrefixStream()).pipe(process.stderr); - } - - if (!pipeStdioToParent && pipeOnlyError) { - // If want to see only errors then show it on the output stream of main process - proc.stderr.pipe(getLogPrefixStream()).pipe(process.stdout); - } - // If there is any error in running the child process, reject the promise proc.on("error", reject); @@ -315,51 +307,25 @@ export async function spawnChildProcess( // If there is a health check, wait for it to pass if (health) { - const startHealthCheckMs = Date.now(); - const intervalId = setInterval(() => { - health() - .then((isHealthy) => { - if (isHealthy.healthy) { - clearInterval(intervalId); - clearTimeout(healthTimeoutId); - proc.removeAllListeners("exit"); - resolve(proc); - } else { - const timeSinceHealthCheckStart = Date.now() - startHealthCheckMs; - if (timeSinceHealthCheckStart > logHealthChecksAfterMs) { - console.log( - `Health check unsuccessful. logPrefix=${logPrefix} pid=${ - proc.pid - } timeSinceHealthCheckStart=${prettyMsToTime(timeSinceHealthCheckStart)}` - ); - } - } - }) - .catch((e) => { - console.error("Error on health check, health functions must never throw", e); - }); - }, healthCheckIntervalMs); - - const healthTimeoutId = setTimeout(() => { - clearTimeout(healthTimeoutId); - - if (intervalId !== undefined) { - reject( - new Error( - `Health check timeout. logPrefix=${logPrefix} pid=${proc.pid} healthTimeout=${prettyMsToTime( - healthTimeoutMs ?? 0 - )}` - ) - ); - } - }, healthTimeoutMs); + try { + await waitForHealth({ + health, + id: logPrefix ?? String(proc.pid as number) ?? "", + logHealthChecksAfterMs, + healthTimeoutMs, + healthCheckIntervalMs, + }); + proc.removeAllListeners("exit"); + resolve(proc); + } catch (error) { + reject( + new Error( + `Health check timeout. logPrefix=${logPrefix} pid=${proc.pid} healthTimeout=${prettyMsToTime(healthTimeoutMs ?? 0)}` + ) + ); + } proc.once("exit", (code: number) => { - if (healthTimeoutId !== undefined) return; - - clearInterval(intervalId); - clearTimeout(healthTimeoutId); - reject( new Error( `Process exited before healthy. logPrefix=${logPrefix} pid=${proc.pid} healthTimeout=${prettyMsToTime( @@ -383,3 +349,58 @@ export function bufferStderr(proc: childProcess.ChildProcessWithoutNullStreams): read: () => data, }; } + +export function handleLoggingForChildProcess( + proc: ChildProcessWithoutNullStreams | ChildProcess, + options: ChildProcessLogOptions +): void { + const {logPrefix, logger, pipeOnlyError, pipeStdioToFile, pipeStdioToParent} = options; + + if (logger && !pipeOnlyError) { + proc.stdout?.on("data", (chunk) => { + logger.debug(Buffer.from(chunk).toString("utf8")); + }); + + proc.stderr?.on("data", (chunk) => { + logger.debug(Buffer.from(chunk).toString("utf8")); + }); + } + + if (logger && pipeOnlyError) { + proc.stderr?.on("data", (chunk) => { + logger.debug(Buffer.from(chunk).toString("utf8")); + }); + } + + const getLogPrefixStream = (): stream.Transform => + new stream.Transform({ + transform(chunk, _encoding, callback) { + callback(null, `[${logPrefix}] [${proc.pid}]: ${Buffer.from(chunk).toString("utf8")}`); + }, + }); + + if (pipeStdioToFile) { + fs.mkdirSync(path.dirname(pipeStdioToFile), {recursive: true}); + const stdoutFileStream = fs.createWriteStream(pipeStdioToFile); + + proc.once("exit", (_code: number) => { + stdoutFileStream.close(); + }); + + if (pipeOnlyError) { + proc.stderr?.pipe(getLogPrefixStream()).pipe(stdoutFileStream); + } else { + proc.stdout?.pipe(getLogPrefixStream()).pipe(stdoutFileStream); + proc.stderr?.pipe(getLogPrefixStream()).pipe(stdoutFileStream); + } + } + + if (pipeStdioToParent) { + if (pipeOnlyError) { + proc.stderr?.pipe(getLogPrefixStream()).pipe(process.stderr); + } else { + proc.stdout?.pipe(getLogPrefixStream()).pipe(process.stdout); + proc.stderr?.pipe(getLogPrefixStream()).pipe(process.stderr); + } + } +} From 26e9bc4b15e8d04fe6af718934aee602da5d0a16 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 10 Jun 2024 19:39:07 +0100 Subject: [PATCH 13/55] fix: add workaround to fix fetching state from checkpointz (#6874) --- packages/api/src/index.ts | 1 + packages/api/src/utils/client/request.ts | 4 +- packages/api/src/utils/headers.ts | 39 ++++++------- packages/api/test/unit/utils/headers.test.ts | 58 +++++++++++++++++++- packages/cli/src/networks/index.ts | 27 ++++++--- 5 files changed, 100 insertions(+), 29 deletions(-) diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index a93fc83d4628..a9a3c075c6a9 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -2,6 +2,7 @@ export * from "./beacon/index.js"; export {HttpStatusCode} from "./utils/httpStatusCode.js"; export {WireFormat} from "./utils/wireFormat.js"; +export {HttpHeader, MediaType} from "./utils/headers.js"; export type {HttpErrorCodes, HttpSuccessCodes} from "./utils/httpStatusCode.js"; export {ApiResponse, HttpClient, FetchError, isFetchError, fetch, defaultInit} from "./utils/client/index.js"; export type {ApiRequestInit} from "./utils/client/request.js"; diff --git a/packages/api/src/utils/client/request.ts b/packages/api/src/utils/client/request.ts index f8b41ca4d706..9c36ad111972 100644 --- a/packages/api/src/utils/client/request.ts +++ b/packages/api/src/utils/client/request.ts @@ -43,7 +43,7 @@ export function createApiRequest( args: E["args"], init: ApiRequestInitRequired ): Request { - const headers = new Headers(init.headers); + const headers = new Headers(); let req: E["request"]; @@ -102,7 +102,7 @@ export function createApiRequest( return new Request(url, { ...init, method: definition.method, - headers: mergeHeaders(headers, req.headers), + headers: mergeHeaders(headers, req.headers, init.headers), body: req.body as BodyInit, }); } diff --git a/packages/api/src/utils/headers.ts b/packages/api/src/utils/headers.ts index 5e39e5765958..0646bb109fbb 100644 --- a/packages/api/src/utils/headers.ts +++ b/packages/api/src/utils/headers.ts @@ -90,28 +90,29 @@ export function setAuthorizationHeader(url: URL, headers: Headers, {bearerToken} } } -export function mergeHeaders(a: HeadersInit | undefined, b: HeadersInit | undefined): Headers { - if (!a) { - return new Headers(b); - } - const headers = new Headers(a); - if (!b) { - return headers; - } - if (Array.isArray(b)) { - for (const [key, value] of b) { - headers.set(key, value); - } - } else if (b instanceof Headers) { - for (const [key, value] of b as unknown as Iterable<[string, string]>) { - headers.set(key, value); +export function mergeHeaders(...headersList: (HeadersInit | undefined)[]): Headers { + const mergedHeaders = new Headers(); + + for (const headers of headersList) { + if (!headers) { + continue; } - } else { - for (const [key, value] of Object.entries(b)) { - headers.set(key, value); + if (Array.isArray(headers)) { + for (const [key, value] of headers) { + mergedHeaders.set(key, value); + } + } else if (headers instanceof Headers) { + for (const [key, value] of headers as unknown as Iterable<[string, string]>) { + mergedHeaders.set(key, value); + } + } else { + for (const [key, value] of Object.entries(headers)) { + mergedHeaders.set(key, value); + } } } - return headers; + + return mergedHeaders; } /** diff --git a/packages/api/test/unit/utils/headers.test.ts b/packages/api/test/unit/utils/headers.test.ts index c909fe59499b..c3bcf6bc79c2 100644 --- a/packages/api/test/unit/utils/headers.test.ts +++ b/packages/api/test/unit/utils/headers.test.ts @@ -1,5 +1,5 @@ import {describe, it, expect} from "vitest"; -import {MediaType, SUPPORTED_MEDIA_TYPES, parseAcceptHeader} from "../../../src/utils/headers.js"; +import {MediaType, SUPPORTED_MEDIA_TYPES, mergeHeaders, parseAcceptHeader} from "../../../src/utils/headers.js"; describe("utils / headers", () => { describe("parseAcceptHeader", () => { @@ -32,4 +32,60 @@ describe("utils / headers", () => { expect(parseAcceptHeader(header, SUPPORTED_MEDIA_TYPES)).toBe(expected); }); }); + + describe("mergeHeaders", () => { + const testCases: {id: string; input: (HeadersInit | undefined)[]; expected: Headers}[] = [ + { + id: "empty headers", + input: [{}, [], new Headers()], + expected: new Headers(), + }, + { + id: "undefined headers", + input: [undefined, undefined], + expected: new Headers(), + }, + { + id: "different headers", + input: [{a: "1"}, {b: "2"}], + expected: new Headers({a: "1", b: "2"}), + }, + { + id: "override on single header", + input: [{a: "1"}, {b: "2"}, {a: "3"}], + expected: new Headers({a: "3", b: "2"}), + }, + { + id: "multiple overrides on same header", + input: [{a: "1"}, {b: "2"}, {a: "3"}, {a: "4"}], + expected: new Headers({a: "4", b: "2"}), + }, + { + id: "multiple overrides on different headers", + input: [{a: "1"}, {b: "2"}, {b: "3"}, {a: "4"}, {c: "5"}], + expected: new Headers({a: "4", b: "3", c: "5"}), + }, + { + id: "headers from array into plain object", + input: [{a: "1"}, [["b", "2"]]], + expected: new Headers({a: "1", b: "2"}), + }, + { + id: "headers from plain object into array", + input: [[["a", "1"]], {b: "2"}], + expected: new Headers({a: "1", b: "2"}), + }, + { + id: "headers from all input types", + input: [[["a", "1"]], {b: "2"}, new Headers({c: "3"}), {d: "4"}], + expected: new Headers({a: "1", b: "2", c: "3", d: "4"}), + }, + ]; + + for (const {id, input, expected} of testCases) { + it(`should correctly merge ${id}`, () => { + expect(mergeHeaders(...input)).toEqual(expected); + }); + } + }); }); diff --git a/packages/cli/src/networks/index.ts b/packages/cli/src/networks/index.ts index 90ba2411e883..2d605335b0e8 100644 --- a/packages/cli/src/networks/index.ts +++ b/packages/cli/src/networks/index.ts @@ -2,10 +2,11 @@ import fs from "node:fs"; import got from "got"; import {ENR} from "@chainsafe/enr"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {WireFormat, getClient} from "@lodestar/api"; +import {HttpHeader, MediaType, WireFormat, getClient} from "@lodestar/api"; +import {getStateTypeFromBytes} from "@lodestar/beacon-node"; import {ChainConfig, ChainForkConfig} from "@lodestar/config"; import {Checkpoint} from "@lodestar/types/phase0"; -import {Slot, ssz} from "@lodestar/types"; +import {Slot} from "@lodestar/types"; import {fromHex, callFnWhenAwait, Logger} from "@lodestar/utils"; import {BeaconStateAllForks, getLatestBlockRoot, computeCheckpointEpochAtStateSlot} from "@lodestar/state-transition"; import {parseBootnodesFile} from "../util/format.js"; @@ -156,18 +157,30 @@ export async function fetchWeakSubjectivityState( } // getStateV2 should be available for all forks including phase0 - const getStatePromise = api.debug.getStateV2({stateId}, {responseWireFormat: WireFormat.ssz}); - - const {stateBytes, fork} = await callFnWhenAwait( + const getStatePromise = api.debug.getStateV2( + {stateId}, + { + responseWireFormat: WireFormat.ssz, + headers: { + // Set Accept header explicitly to fix Checkpointz incompatibility + // See https://github.com/ethpandaops/checkpointz/issues/165 + [HttpHeader.Accept]: MediaType.ssz, + }, + } + ); + + const stateBytes = await callFnWhenAwait( getStatePromise, () => logger.info("Download in progress, please wait..."), GET_STATE_LOG_INTERVAL ).then((res) => { - return {stateBytes: res.ssz(), fork: res.meta().version}; + return res.ssz(); }); logger.info("Download completed", {stateId}); - const wsState = ssz.allForks[fork].BeaconState.deserializeToViewDU(stateBytes); + // It should not be required to get fork type from bytes but Checkpointz does not return + // Eth-Consensus-Version header, see https://github.com/ethpandaops/checkpointz/issues/164 + const wsState = getStateTypeFromBytes(config, stateBytes).deserializeToViewDU(stateBytes); return { wsState, From 4e86094453a34bb8cf9bad4224cb770fdf21bbdc Mon Sep 17 00:00:00 2001 From: Julien Date: Tue, 11 Jun 2024 02:52:20 -0700 Subject: [PATCH 14/55] chore: remove unused code (#6873) --- packages/light-client/src/transport/rest.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/light-client/src/transport/rest.ts b/packages/light-client/src/transport/rest.ts index 770c002be14f..bc19594e3930 100644 --- a/packages/light-client/src/transport/rest.ts +++ b/packages/light-client/src/transport/rest.ts @@ -49,11 +49,6 @@ export class LightClientRestTransport implements LightClientTransport { return {version: res.meta().version, data: res.value()}; } - async fetchBlock(blockRootAsString: string): Promise<{version: ForkName; data: allForks.SignedBeaconBlock}> { - const res = await this.api.beacon.getBlockV2({blockId: blockRootAsString}); - return {version: res.meta().version, data: res.value()}; - } - onOptimisticUpdate(handler: (optimisticUpdate: allForks.LightClientOptimisticUpdate) => void): void { this.subscribeEventstream(); this.eventEmitter.on(routes.events.EventType.lightClientOptimisticUpdate, handler); From 3be656bb2279c9ed5ab8d3117e368d02b21c5046 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 11 Jun 2024 16:02:09 +0100 Subject: [PATCH 15/55] fix: ignore empty ids/statuses arrays when filtering validators (#6876) fix: ignore empty ids/statuses when filtering validators --- packages/beacon-node/src/api/impl/beacon/state/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 e5592d461ff7..4695368449c7 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/index.ts @@ -75,20 +75,20 @@ export function getBeaconStateApi({ }; }, - async getStateValidators({stateId, validatorIds, statuses}) { + async getStateValidators({stateId, validatorIds = [], statuses = []}) { const {state, executionOptimistic, finalized} = await resolveStateId(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; const validatorResponses: routes.beacon.ValidatorResponse[] = []; - if (validatorIds) { + if (validatorIds.length) { for (const id of validatorIds) { const resp = getStateValidatorIndex(id, state, pubkey2index); if (resp.valid) { const validatorIndex = resp.validatorIndex; const validator = validators.getReadonly(validatorIndex); - if (statuses && !statuses.includes(getValidatorStatus(validator, currentEpoch))) { + if (statuses.length && !statuses.includes(getValidatorStatus(validator, currentEpoch))) { continue; } const validatorResponse = toValidatorResponse( @@ -104,7 +104,7 @@ export function getBeaconStateApi({ data: validatorResponses, meta: {executionOptimistic, finalized}, }; - } else if (statuses) { + } else if (statuses.length) { const validatorsByStatus = filterStateValidatorsByStatus(statuses, state, pubkey2index, currentEpoch); return { data: validatorsByStatus, From 93322168bbf4b9ef77acd2697db5dfff34d11508 Mon Sep 17 00:00:00 2001 From: Julien Date: Wed, 12 Jun 2024 01:30:41 -0700 Subject: [PATCH 16/55] docs: revert docs link removal (#6864) * fix: revert docs link removal * chore: address comments * update links and fix broken links * fix lint * update wordlist * fix wordlist sort --------- Co-authored-by: Phil Ngo --- .gitignore | 4 ++-- .wordlist.txt | 2 ++ README.md | 2 +- .../advanced-topics/setting-up-a-testnet.md | 20 +++++++++---------- docs/pages/index.md | 19 +++++++++--------- .../run/beacon-management/data-retention.md | 2 +- .../run/beacon-management/starting-a-node.md | 13 +++++++----- .../pages/run/getting-started/installation.md | 4 ++-- .../validator-management/vc-configuration.md | 18 ++++++++--------- docs/pages/supporting-libraries/index.md | 2 +- docs/sidebars.ts | 14 ++++++------- packages/api/README.md | 2 +- packages/beacon-node/README.md | 2 +- packages/cli/README.md | 2 +- packages/reqresp/README.md | 2 +- packages/validator/README.md | 2 +- 16 files changed, 56 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index a6d12b02445b..073b21cf322a 100644 --- a/.gitignore +++ b/.gitignore @@ -48,8 +48,8 @@ docs/pages/**/*-cli.md docs/pages/assets docs/pages/images docs/pages/security.md -docs/pages/lightclient-prover/lightclient.md -docs/pages/lightclient-prover/prover.md +docs/pages/libraries/lightclient-prover/lightclient.md +docs/pages/libraries/lightclient-prover/prover.md docs/pages/api/api-reference.md docs/pages/contribution/getting-started.md ## Docusaurus diff --git a/.wordlist.txt b/.wordlist.txt index dbb4f707af72..46ca81441348 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -78,6 +78,8 @@ PoS Prysm Quickstart RPC +Reth +Ryzen SHA SSD SSZ diff --git a/README.md b/README.md index 8546a467f404..4abb03554957 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ - :gear: Follow the installation method for [source install](https://chainsafe.github.io/lodestar/getting-started/installation/#build-from-source), [NPM install](https://chainsafe.github.io/lodestar/getting-started/installation/#install-from-npm-not-recommended), or [Docker install](https://chainsafe.github.io/lodestar/getting-started/installation/#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). - :books: Use [Lodestar libraries](https://chainsafe.github.io/lodestar/supporting-libraries/libraries/) in your next Ethereum Typescript project. - :globe_with_meridians: Run a beacon node on [mainnet or a public testnet](https://chainsafe.github.io/lodestar/getting-started/starting-a-node/). -- :computer: Utilize the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/advanced-topics/setting-up-a-testnet/). +- :computer: Utilize the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/contribution/advanced-topics/setting-up-a-testnet/). - :spiral_notepad: View the Lodestar [CLI commands and options](https://chainsafe.github.io/lodestar/reference/cli/). - :nerd_face: View the [Package and dependency structure](https://chainsafe.github.io/lodestar/contribution/depgraph/). - :memo: Prospective contributors can read the [contributing section](./CONTRIBUTING.md) to understand how we develop and test on Lodestar. 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 1d6e38941ea9..09d4735931a8 100644 --- a/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md +++ b/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md @@ -21,10 +21,10 @@ Run a beacon node as a **bootnode**, with 8 validators with the following comman --reset ``` -`--genesisValidators` and `--genesisTime` define the genesis state of the beacon chain. `--dataDir` defines a path where -lodestar should store the beacon state. -`--enr.ip` sets the ENR IP entry for the node (essential for second node to connect via `enr`) and `--enr.udp` exposes the `discv5` discovery service (if you want to connect more than 1 node and enable discovery amongst them via _bootnode_). -Lastly the `--reset` flag ensures the state is cleared on each restart - which is useful when testing locally. +[`--genesisValidators`](../dev-cli.md#--genesisvalidators) and [`--genesisTime`](../dev-cli.md#--genesistime) define the genesis state of the beacon chain. [`--dataDir`](../dev-cli.md#--datadir) defines a path where +Lodestar should store the beacon state. +[`--enr.ip`](../dev-cli.md#--enrip) sets the ENR IP entry for the node (essential for second node to connect via `enr`) and [`--enr.udp`](../dev-cli.md#--enrudp) exposes the Discv5 discovery service (if you want to connect more than 1 node and enable discovery amongst them via _bootnode_). +Lastly the [`--reset`](../dev-cli.md#--reset) flag ensures the state is cleared on each restart - which is useful when testing locally. Once the node has started, make a request to `curl http://localhost:9596/eth/v1/node/identity` and copy the `enr` value. @@ -49,16 +49,16 @@ Start the second node without starting any validators and connect to the first n --reset ``` -By default, lodestar starts as many validators as the number supplied by `--genesisValidators`. In order to not start any validator, this is overridden by -the `--startValidators` option. Passing a value of `0..0` means no validators should be started. +By default, Lodestar starts as many validators as the number supplied by [`--genesisValidators`](../dev-cli.md#--genesisvalidators). In order to not start any validator, this is overridden by +the [`--startValidators`](../dev-cli.md#--startvalidators) option. Passing a value of `0..0` means no validators should be started. -Also, take note that the values of `--genesisValidators` and `--genesisTime` must be the same as the ones passed to the first node in order for the two nodes +Also, take note that the values of [`--genesisValidators`](../dev-cli.md#--genesisvalidators) and [`--genesisTime`](../dev-cli.md#--genesistime) must be the same as the ones passed to the first node in order for the two nodes to have the same beacon chain. -Also `--port` and `--rest.port` are supplied since the default values will already be in use by the first node. +Also [`--port`](../dev-cli.md#--port) and [`--rest.port`](../dev-cli.md#--restport) are supplied since the default values will already be in use by the first node. The `--network.connectToDiscv5Bootnodes` flags needs to be set to true as this is needed to allow connection to boot ENRs on local devnet. -The exact ENR of node to connect to is then supplied via the `--bootnodes` flag. +The exact ENR of node to connect to is then supplied via the [`--bootnodes`](../dev-cli.md#--bootnodes) flag. Once the second node starts, you should see an output similar to the following in either of the terminals: @@ -93,7 +93,7 @@ will give a result similar to the following: ## Post-Merge local testnet -To set up a local testnet with a Post-Merge configuration, you may need to add the following parameters (in addition to the parameters described above) to your `lodestar dev` command: +To set up a local testnet with a Post-Merge configuration, you may need to add the following parameters (in addition to the parameters described above) to your [`lodestar dev`](../dev-cli.md#dev-options) command: - `--params.ALTAIR_FORK_EPOCH 0` - `--params.BELLATRIX_FORK_EPOCH 0` diff --git a/docs/pages/index.md b/docs/pages/index.md index 7948cb9f8b57..6f2faa4b1de4 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -10,26 +10,25 @@ title: Home ### Getting started -- Install [binaries](./run/getting-started/installation.md/#binaries), follow the installation method for [source install](./run/getting-started/installation.md/#build-from-source) or [Docker install](./run/getting-started/installation.md/#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). +- Install [binaries](./run/getting-started/installation/#binaries), follow the installation method for [source install](./run/getting-started/installation/#build-from-source) or [Docker install](./run/getting-started/installation/#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). - Use [Lodestar libraries](./supporting-libraries/index.md) in your next Ethereum Typescript project. - Run a beacon node on [mainnet or a public testnet](./run/beacon-management/starting-a-node.md). - Utilize the whole stack by [starting a local testnet](./contribution/advanced-topics/setting-up-a-testnet.md). -- View the Lodestar [CLI commands and options](./run/beacon-management/beacon-cli.md) +- View the Lodestar Beacon [CLI commands and options](./run/beacon-management/beacon-cli.md) - Prospective contributors can read the [contributing section](./contribution/getting-started.md) to understand how we develop and test on Lodestar. -- If you have questions [submit an issue](https://github.com/ChainSafe/lodestar/issues/new) or join us on [Discord](https://discord.gg/yjyvFRP)! +- If you have questions [submit an issue](https://github.com/ChainSafe/lodestar/issues/new/choose) or join us on [Discord](https://discord.gg/yjyvFRP)! - Please note our [security policy](./security.md). -- Sign up to our [mailing list](https://chainsafe.typeform.com/lodestar) for announcements and any critical information about Lodestar. ## Specifications Hardware specifications minimum / recommended, to run the Lodestar client. -| | Minimum | Recommended | -| --------- | -------------------------------- | --------------------------------- | -| Processor | Intel Core i5–760 or AMD FX-8100 | Intel Core i7–4770 or AMD FX-8310 | -| Memory | 4GB RAM | 8GB RAM | -| Storage | 20GB available space SSD | 100GB available space SSD | -| Internet | Broadband connection | Broadband connection | +| | Minimum | Recommended | +| --------- | -------------------------------------- | -------------------------------------- | +| Processor | Intel Core i3–9100 or AMD Ryzen 5 3450 | Intel Core i7–9700 or AMD Ryzen 7 4700 | +| Memory | 16GB RAM | 32GB RAM | +| Storage | 100GB available space SSD | 1TB available space SSD | +| Internet | Broadband connection | Broadband connection | ## About these docs diff --git a/docs/pages/run/beacon-management/data-retention.md b/docs/pages/run/beacon-management/data-retention.md index 57fa5dde0f55..3e6b8a9899a1 100644 --- a/docs/pages/run/beacon-management/data-retention.md +++ b/docs/pages/run/beacon-management/data-retention.md @@ -51,4 +51,4 @@ Configuring your node to store and prune data is key to success. On average you Logs can also become quite large so please check out the section on [log management](../logging-and-metrics/log-management.md) for more information. -There is really only one flag that is needed to manage the data for Lodestar, [`--dataDir`](./beacon-management/beacon-cli#--datadir). Other than that handling log management is really the heart of the data management story. Beacon node data is what it is. Depending on the execution client that is chosen, there may be flags to help with data storage growth but that is outside the scope of this document. +There is really only one flag that is needed to manage the data for Lodestar, [`--dataDir`](./beacon-cli#--datadir). Other than that handling log management is really the heart of the data management story. Beacon node data is what it is. Depending on the execution client that is chosen, there may be flags to help with data storage growth but that is outside the scope of this document. diff --git a/docs/pages/run/beacon-management/starting-a-node.md b/docs/pages/run/beacon-management/starting-a-node.md index 86c03c621c4e..6838e14304d6 100644 --- a/docs/pages/run/beacon-management/starting-a-node.md +++ b/docs/pages/run/beacon-management/starting-a-node.md @@ -39,16 +39,19 @@ When starting up a Lodestar beacon node in any configuration, ensure you add the ### Ensure JWT is configured with your execution node **For Go Ethereum:** -Use the `--authrpc.jwtsecret /data/jwtsecret` flag to configure the secret. Use their documentation [here](https://geth.ethereum.org/docs/interface/merge). +Use the `--authrpc.jwtsecret /path/to/jwtsecret.hex` flag to configure the secret. Use their documentation [here](https://geth.ethereum.org/docs/getting-started#start-geth). **For Nethermind:** -Use the `--JsonRpc.JwtSecretFile /data/jwtsecret` flag to configure the secret. Use their documentation [here](https://docs.nethermind.io/nethermind/first-steps-with-nethermind/running-nethermind-post-merge#jwtsecretfile). +Use the `--JsonRpc.JwtSecretFile /path/to/jwtsecret.hex` flag to configure the secret. Use their documentation [here](https://docs.nethermind.io/get-started/consensus-clients/#configuring-json-rpc-interface). **For Besu:** -Use the `--engine-jwt-secret=` flag to configure the secret. Use their documentation [here](https://besu.hyperledger.org/en/stable/Reference/CLI/CLI-Syntax/#engine-jwt-secret). +Use the `--engine-jwt-secret=/path/to/jwtsecret.hex` flag to configure the secret. Use their documentation [here](https://besu.hyperledger.org/public-networks/how-to/use-besu-api/authenticate#2-create-the-jwt). **For Erigon:** -Use the `--authrpc.jwtsecret` flag to configure the secret. Use their documentation [here](https://github.com/ledgerwatch/erigon?tab=readme-ov-file#beacon-chain-consensus-layer). +Use the `--authrpc.jwtsecret /path/to/jwtsecret.hex` flag to configure the secret. Use their documentation [here](https://github.com/ledgerwatch/erigon?tab=readme-ov-file#beacon-chain-consensus-layer). + +**For Reth:** +Use the `--authrpc.jwtsecret /path/to/jwtsecret.hex` flag to configure the secret. Use their documentation [here](https://reth.rs/run/mainnet.html?highlight=jwt#running-the-reth-node). ## Run a beacon node @@ -92,7 +95,7 @@ If your node is stuck with `Searching for peers` review your network configurati By default, Lodestar stores all configuration and chain data at the path `$XDG_DATA_HOME/lodestar/$NETWORK_NAME`. -A young testnet should take a few hours to sync. If you see multiple or consistent errors in the logs, please open a [Github issue](https://github.com/ChainSafe/lodestar/issues/new) or reach out to us in [Discord](https://discord.gg/yjyvFRP). Just by reporting anomalies you are helping accelerate the progress of Ethereum Consensus, thanks for contributing! +A young testnet should take a few hours to sync. If you see multiple or consistent errors in the logs, please open a [Github issue](https://github.com/ChainSafe/lodestar/issues/new/choose) or reach out to us in [Discord](https://discord.gg/yjyvFRP). Just by reporting anomalies you are helping accelerate the progress of Ethereum Consensus, thanks for contributing! :::warning It is dangerous to expose your Beacon APIs publicly as there is no default authentication mechanism provided. Ensure your beacon node host is not exposing ports 8545 or 9596 outside of your internal network. diff --git a/docs/pages/run/getting-started/installation.md b/docs/pages/run/getting-started/installation.md index b5ea6b74f5fb..52cb2770725b 100644 --- a/docs/pages/run/getting-started/installation.md +++ b/docs/pages/run/getting-started/installation.md @@ -2,7 +2,7 @@ ## Binaries -Binaries can be downloaded from [this page](https://github.com/ChainSafe/lodestar/releases/) under the `Assets` section. +Binaries can be downloaded from [the release page](https://github.com/ChainSafe/lodestar/releases/latest) under the `Assets` section. ## Docker Installation @@ -85,7 +85,7 @@ Lodestar should now be ready for use. ./lodestar --help ``` -See [Command Line Reference](./../../reference/cli.md) for further information. +See [Command Line Reference](./../beacon-management/beacon-cli.md) for further information. ### Known Issues diff --git a/docs/pages/run/validator-management/vc-configuration.md b/docs/pages/run/validator-management/vc-configuration.md index 077d3f479139..ce6db52b3a66 100644 --- a/docs/pages/run/validator-management/vc-configuration.md +++ b/docs/pages/run/validator-management/vc-configuration.md @@ -1,5 +1,5 @@ --- -title: Stake with a Validator Client +title: Starting a Validator Client --- # Validator Configuration @@ -30,7 +30,7 @@ You will also need the passphrase used the encrypt the keystore. This can be spe #### Option 1: Import Keys To Lodestar's Keystores Folder -You can load the keys into the keystore folder using the `validator import` command. There are two methods for importing keystores: +You can load the keys into the keystore folder using the [`validator import`](../validator-management/validator-cli.md#validator-import) command. There are two methods for importing keystores: _Interactive passphrase import_ @@ -50,13 +50,13 @@ The interactive passphrase import method will prompt every keystore in the `vali The plaintext passphrase file import method will allow you to import all keystores in the `validator_keys` folder encrypted with the same password contained in `password.txt` for efficiency. ::: -Once imported with either method, these keystores will be automatically loaded when you start the validator. To list the imported keystores, use the `validator list` command. +Once imported with either method, these keystores will be automatically loaded when you start the validator. To list the imported keystores, use the [`validator list`](./validator-cli.md#validator-list) command. --- #### Option 2: Import Keys When Starting the Validator -To import keys when you start the validator specify the `--importKeystores` and `--importKeystoresPassword` flags with the `validator` command: +To import keys when you start the validator specify the [`--importKeystores`](./validator-cli.md#--importkeystores) and [`--importKeystoresPassword`](./validator-cli.md#--importkeystorespassword) flags with the [`validator`](./validator-cli.md#base-validator-command) command: ```bash ./lodestar validator --importKeystores ./validator_keys --importKeystoresPassword ./password.txt @@ -70,17 +70,17 @@ If you import keys using `--importKeystores` at runtime (Option 2) any keys load Post-Merge Ethereum requires validators to set a **Fee Recipient** which allows you to receive priority fees when proposing blocks. If you do not set this address, your priority fees will be sent to the [burn address](https://etherscan.io/address/0x0000000000000000000000000000000000000000). -Configure your validator client's fee recipient address by using the `--suggestedFeeRecipient` flag. Ensure you specify an Ethereum address you control. An example of a fee recipient set with the address `0xB7576e9d314Df41EC5506494293Afb1bd5D3f65d` would add the following flag to their configuration: `--suggestedFeeRecipient 0xB7576e9d314Df41EC5506494293Afb1bd5D3f65d`. +Configure your validator client's fee recipient address by using the [`--suggestedFeeRecipient`](./validator-cli.md#--suggestedfeerecipient) flag. Ensure you specify an Ethereum address you control. An example of a fee recipient set with the address `0xB7576e9d314Df41EC5506494293Afb1bd5D3f65d` would add the following flag to their configuration: `--suggestedFeeRecipient 0xB7576e9d314Df41EC5506494293Afb1bd5D3f65d`. -You may choose to use the `--strictFeeRecipientCheck` flag to enable a strict check of the fee recipient address with the one returned by the beacon node for added reassurance. +You may choose to use the [`--strictFeeRecipientCheck`](./validator-cli.md#--strictfeerecipientcheck) flag to enable a strict check of the fee recipient address with the one returned by the beacon node for added reassurance. ### Configure your builder selection and/or builder boost factor If you are running a beacon node with connected builder relays, you may use these validator configurations to signal which block (builder vs. local execution) the beacon node should produce. -With produceBlockV3 (enabled automatically after the Deneb hard fork), the `--builder.boostFactor` is a percentage multiplier the block producing beacon node must apply to boost (>100) or dampen (<100) builder block value for selection against execution block. The multiplier is ignored if `--builder.selection` is set to anything other than `maxprofit`. Even though this is set on the validator client, the calculation is requested and applied on the beacon node itself. For more information, see the [produceBlockV3 Beacon API](https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/produceBlockV3). +With produceBlockV3 (enabled automatically after the Deneb hard fork), the [`--builder.boostFactor`](./validator-cli.md#--builderboostfactor) is a percentage multiplier the block producing beacon node must apply to boost (>100) or dampen (<100) builder block value for selection against execution block. The multiplier is ignored if [`--builder.selection`](./validator-cli.md#--builderselection) is set to anything other than `maxprofit`. Even though this is set on the validator client, the calculation is requested and applied on the beacon node itself. For more information, see the [produceBlockV3 Beacon API](https://ethereum.github.io/beacon-APIs/#/ValidatorRequiredApi/produceBlockV3). -With Lodestar's `--builder.selection` validator options, you can select: +With Lodestar's [`--builder.selection`](./validator-cli.md#--builderselection) validator options, you can select: - `default`: Default setting for Lodestar set at `--builder.boostFactor=90`. This default setting will have a local block boost of ~10%. Note that this value might change in the future depending on what we think is the most appropriate value to help improve censorship resistance of Ethereum. - `maxprofit`: An alias of `--builder.boostFactor=100`, which will always choose the more profitable block. Using this option, you may customize your `--builder.boostFactor` to your preference. Examples of its usage are below. @@ -91,7 +91,7 @@ With Lodestar's `--builder.selection` validator options, you can select: #### Calculating builder boost factor with examples -To calculate the builder boost factor setting, you need to know what percentage you will accept a builder block for against a local execution block using the following formula: `100*100/(100+percentage)`. The value passed to `--builder.boostFactor` must be a valid number without decimals. +To calculate the builder boost factor setting, you need to know what percentage you will accept a builder block for against a local execution block using the following formula: `100*100/(100+percentage)`. The value passed to [`--builder.boostFactor`](./validator-cli.md#--builderboostfactor) must be a valid number without decimals. Example 1: I will only accept a builder block with 25% more value than the local execution block. diff --git a/docs/pages/supporting-libraries/index.md b/docs/pages/supporting-libraries/index.md index 555294393ec1..ad7e37ec278c 100644 --- a/docs/pages/supporting-libraries/index.md +++ b/docs/pages/supporting-libraries/index.md @@ -4,7 +4,7 @@ ### LibP2P -- [`@chainsafe/js-libp2p-noise`](https://github.com/NodeFactoryIo/js-libp2p-noise) - [Noise](https://noiseprotocol.org/noise.html) handshake for `js-libp2p` +- [`@chainsafe/js-libp2p-noise`](https://github.com/ChainSafe/js-libp2p-noise) - [Noise](https://noiseprotocol.org/noise.html) handshake for `js-libp2p` - [`@chainsafe/js-libp2p-gossipsub`](https://github.com/ChainSafe/js-libp2p-gossipsub) - [Gossipsub](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) protocol for `js-libp2p` - [`@chainsafe/libp2p-yamux`](https://github.com/ChainSafe/js-libp2p-yamux) diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 47825d48b47a..a82421a961cb 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -3,16 +3,18 @@ import type {SidebarsConfig} from "@docusaurus/plugin-content-docs"; const sidebars: SidebarsConfig = { tutorialSidebar: [ "index", + "introduction", + "security", { type: "category", label: "Run A Node", + collapsed: false, items: [ "run/getting-started/quick-start", "run/getting-started/installation", { type: "category", label: "Beacon node", - collapsed: false, items: [ "run/beacon-management/starting-a-node", "run/beacon-management/beacon-cli", @@ -25,7 +27,6 @@ const sidebars: SidebarsConfig = { { type: "category", label: "Validator Client", - collapsed: false, items: [ "run/validator-management/vc-configuration", "run/validator-management/validator-cli", @@ -35,13 +36,11 @@ const sidebars: SidebarsConfig = { { type: "category", label: "Logging and Metrics", - collapsed: false, items: ["run/logging-and-metrics/prometheus-grafana", "run/logging-and-metrics/client-monitoring"], }, { type: "category", label: "Discv5 Bootnode", - collapsed: false, items: ["run/bootnode/bootnode-cli"], }, ], @@ -53,12 +52,12 @@ const sidebars: SidebarsConfig = { items: [ { type: "category", - label: "Lodestar Light Client", + label: "Light Client", items: ["libraries/lightclient-prover/lightclient-cli", "libraries/lightclient-prover/lightclient"], }, { type: "category", - label: "Lodestar Light Prover", + label: "Prover", items: ["libraries/lightclient-prover/prover"], }, ], @@ -73,7 +72,6 @@ const sidebars: SidebarsConfig = { { type: "category", label: "Advanced Topics", - collapsed: false, items: ["contribution/advanced-topics/setting-up-a-testnet"], }, "contribution/depgraph", @@ -81,6 +79,7 @@ const sidebars: SidebarsConfig = { type: "category", label: "Development Tools", items: [ + "contribution/dev-cli", "contribution/tools/debugging", "contribution/tools/flamegraphs", "contribution/tools/heap-dumps", @@ -90,7 +89,6 @@ const sidebars: SidebarsConfig = { { type: "category", label: "Testing", - collapsed: false, items: [ "contribution/testing/index", "contribution/testing/end-to-end-tests", diff --git a/packages/api/README.md b/packages/api/README.md index 5a1178e9c766..3bcd397a9139 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -45,7 +45,7 @@ You will need to go over the [specification](https://github.com/ethereum/beacon- ## Getting started - Follow the [installation guide](https://chainsafe.github.io/lodestar/) to install Lodestar. -- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/advanced-topics/setting-up-a-testnet/). +- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/contribution/advanced-topics/setting-up-a-testnet/). ## Contributors diff --git a/packages/beacon-node/README.md b/packages/beacon-node/README.md index 92e3f38787b7..f60764164620 100644 --- a/packages/beacon-node/README.md +++ b/packages/beacon-node/README.md @@ -18,7 +18,7 @@ You will need to go over the [specification](https://github.com/ethereum/consens ## Getting started - Follow the [installation guide](https://chainsafe.github.io/lodestar/) to install Lodestar. -- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/advanced-topics/setting-up-a-testnet/). +- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/contribution/advanced-topics/setting-up-a-testnet/). ## Contributors diff --git a/packages/cli/README.md b/packages/cli/README.md index 028661af856a..e33fda9a03c7 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -12,7 +12,7 @@ Command line tool for Lodestar ## Getting started - Follow the [installation guide](https://chainsafe.github.io/lodestar/) to install Lodestar. -- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/advanced-topics/setting-up-a-testnet/). +- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/contribution/advanced-topics/setting-up-a-testnet/). ### Lodestar diff --git a/packages/reqresp/README.md b/packages/reqresp/README.md index 65d0c43b47b0..38a6fbe39653 100644 --- a/packages/reqresp/README.md +++ b/packages/reqresp/README.md @@ -53,7 +53,7 @@ You will need to go over the [specification](https://github.com/ethereum/beacon- ## Getting started - Follow the [installation guide](https://chainsafe.github.io/lodestar/) to install Lodestar. -- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/advanced-topics/setting-up-a-testnet/). +- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/contribution/advanced-topics/setting-up-a-testnet/). ## Contributors diff --git a/packages/validator/README.md b/packages/validator/README.md index b29972acf8ac..fa853519ecfe 100644 --- a/packages/validator/README.md +++ b/packages/validator/README.md @@ -13,7 +13,7 @@ eth-consensus api compatible beacon nodes/databases/loggers. ## Getting started - Follow the [installation guide](https://chainsafe.github.io/lodestar/) to install Lodestar. -- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/advanced-topics/setting-up-a-testnet/). +- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/contribution/advanced-topics/setting-up-a-testnet/). ## License From 53d75e4a6d62baddd56aed875bca63def90874ba Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 12 Jun 2024 15:59:18 +0100 Subject: [PATCH 17/55] docs: another review (#6879) --- README.md | 6 +++--- .../contribution/advanced-topics/setting-up-a-testnet.md | 2 +- docs/pages/faqs.md | 2 +- docs/pages/index.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4abb03554957..4afb92d4e92d 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,14 @@ ## Getting started -- :gear: Follow the installation method for [source install](https://chainsafe.github.io/lodestar/getting-started/installation/#build-from-source), [NPM install](https://chainsafe.github.io/lodestar/getting-started/installation/#install-from-npm-not-recommended), or [Docker install](https://chainsafe.github.io/lodestar/getting-started/installation/#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). +- :gear: Follow the instructions for [build from source](https://chainsafe.github.io/lodestar/run/getting-started/installation#build-from-source), [binaries](https://chainsafe.github.io/lodestar/run/getting-started/installation#binaries), or [Docker](https://chainsafe.github.io/lodestar/run/getting-started/installation#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). - :books: Use [Lodestar libraries](https://chainsafe.github.io/lodestar/supporting-libraries/libraries/) in your next Ethereum Typescript project. -- :globe_with_meridians: Run a beacon node on [mainnet or a public testnet](https://chainsafe.github.io/lodestar/getting-started/starting-a-node/). +- :globe_with_meridians: Run a beacon node on [mainnet or a public testnet](https://chainsafe.github.io/lodestar/run/beacon-management/starting-a-node/). - :computer: Utilize the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/contribution/advanced-topics/setting-up-a-testnet/). - :spiral_notepad: View the Lodestar [CLI commands and options](https://chainsafe.github.io/lodestar/reference/cli/). - :nerd_face: View the [Package and dependency structure](https://chainsafe.github.io/lodestar/contribution/depgraph/). - :memo: Prospective contributors can read the [contributing section](./CONTRIBUTING.md) to understand how we develop and test on Lodestar. -- :writing_hand: If you have questions [submit an issue](https://github.com/ChainSafe/lodestar/issues/new) or join us on [Discord](https://discord.gg/yjyvFRP)! +- :writing_hand: If you have questions [submit an issue](https://github.com/ChainSafe/lodestar/issues/new/choose) or join us on [Discord](https://discord.gg/yjyvFRP)! [![Discord](https://img.shields.io/discord/593655374469660673.svg?label=Discord&logo=discord)](https://discord.gg/aMxzVcr) - :rotating_light: Please note our [security policy](./SECURITY.md). - :bird: Follow Lodestar on [Twitter](https://twitter.com/lodestar_eth) for announcements and updates! [![Twitter Follow](https://img.shields.io/twitter/follow/lodestar_eth)](https://twitter.com/lodestar_eth) 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 09d4735931a8..f7c4bfece237 100644 --- a/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md +++ b/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md @@ -4,7 +4,7 @@ title: Setting Up a Testnet # 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 +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. **Terminal 1** diff --git a/docs/pages/faqs.md b/docs/pages/faqs.md index 8c6351a2438d..e7ac490bf9b9 100644 --- a/docs/pages/faqs.md +++ b/docs/pages/faqs.md @@ -37,7 +37,7 @@ NODE_OPTIONS: --max-old-space-size=8192 Lodestar reads all environment variables prefixed with `LODESTAR` and will try to parse them similar to command line arguments, meaning any unknown argument will cause an error. -``` +```txt ✖ Unknown arguments: servicePort, servicePortEthConsensusP2p, port9000Tcp, port9000TcpPort, port9000TcpProto, port9000TcpAddr, serviceHost ``` diff --git a/docs/pages/index.md b/docs/pages/index.md index 6f2faa4b1de4..c74ad6470d7d 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -10,7 +10,7 @@ title: Home ### Getting started -- Install [binaries](./run/getting-started/installation/#binaries), follow the installation method for [source install](./run/getting-started/installation/#build-from-source) or [Docker install](./run/getting-started/installation/#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). +- Follow the instructions for [build from source](./run/getting-started/installation#build-from-source), [binaries](./run/getting-started/installation#binaries), or [Docker](./run/getting-started/installation#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). - Use [Lodestar libraries](./supporting-libraries/index.md) in your next Ethereum Typescript project. - Run a beacon node on [mainnet or a public testnet](./run/beacon-management/starting-a-node.md). - Utilize the whole stack by [starting a local testnet](./contribution/advanced-topics/setting-up-a-testnet.md). From f4460cd1d6e766bc8800f833caf9a694c1a1e809 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 13 Jun 2024 11:36:59 +0100 Subject: [PATCH 18/55] fix: ignore empty array when filtering validator balances (#6878) --- packages/beacon-node/src/api/impl/beacon/state/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 4695368449c7..201cc875123e 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/index.ts @@ -151,10 +151,10 @@ export function getBeaconStateApi({ }; }, - async getStateValidatorBalances({stateId, validatorIds}) { + async getStateValidatorBalances({stateId, validatorIds = []}) { const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); - if (validatorIds) { + if (validatorIds.length) { const headState = chain.getHeadState(); const balances: routes.beacon.ValidatorBalance[] = []; for (const id of validatorIds) { From 225e67a1e5314958c59f7189d1a7fe0335606dc9 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 13 Jun 2024 14:28:03 +0100 Subject: [PATCH 19/55] fix: allow POST requests without body / content-type header (#6881) This is not possible for most routes as request will fail schema validation --- packages/api/src/utils/server/handler.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/api/src/utils/server/handler.ts b/packages/api/src/utils/server/handler.ts index 162f68f25c05..fc2d1fe6ae6e 100644 --- a/packages/api/src/utils/server/handler.ts +++ b/packages/api/src/utils/server/handler.ts @@ -65,15 +65,22 @@ export function createFastifyHandler( returnBytes: responseWireFormat === WireFormat.ssz, }); } else { + let requestWireFormat: WireFormat; const contentType = req.headers[HttpHeader.ContentType]; - if (contentType === undefined) { - throw new ApiError(400, "Content-Type header is required"); + if (contentType === undefined && req.body === undefined) { + // Default to json parser if body is omitted. This is not possible for most + // routes as request will fail schema validation before this handler is called + requestWireFormat = WireFormat.json; + } else { + if (contentType === undefined) { + throw new ApiError(400, "Content-Type header is required"); + } + const requestMediaType = parseContentTypeHeader(contentType); + if (requestMediaType === null) { + throw new ApiError(415, `Unsupported media type: ${contentType.split(";", 1)[0]}`); + } + requestWireFormat = getWireFormat(requestMediaType); } - const requestMediaType = parseContentTypeHeader(contentType); - if (requestMediaType === null) { - throw new ApiError(415, `Unsupported media type: ${contentType.split(";", 1)[0]}`); - } - const requestWireFormat = getWireFormat(requestMediaType); const {onlySupport} = definition.req as RequestWithBodyCodec; if (onlySupport !== undefined && onlySupport !== requestWireFormat) { From 79a008f0c847527b46597364ff79c13650c5c0e2 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 13 Jun 2024 14:28:58 +0100 Subject: [PATCH 20/55] fix: ignore empty array when filtering validator rewards (#6882) --- .../beacon-node/src/chain/rewards/attestationsRewards.ts | 6 +++--- .../beacon-node/src/chain/rewards/syncCommitteeRewards.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/beacon-node/src/chain/rewards/attestationsRewards.ts b/packages/beacon-node/src/chain/rewards/attestationsRewards.ts index a59aa6a2b4d2..3b4583826349 100644 --- a/packages/beacon-node/src/chain/rewards/attestationsRewards.ts +++ b/packages/beacon-node/src/chain/rewards/attestationsRewards.ts @@ -137,19 +137,19 @@ function computeTotalAttestationsRewardsAltair( transitionCache: EpochTransitionCache, idealRewards: IdealAttestationsReward[], penalties: AttestationsPenalty[], - validatorIds?: (ValidatorIndex | string)[] // validatorIds filter + validatorIds: (ValidatorIndex | string)[] = [] ): TotalAttestationsReward[] { const rewards = []; const {statuses} = transitionCache; const {epochCtx, config} = state; const validatorIndices = validatorIds - ?.map((id) => (typeof id === "number" ? id : epochCtx.pubkey2index.get(id))) + .map((id) => (typeof id === "number" ? id : epochCtx.pubkey2index.get(id))) .filter((index) => index !== undefined); // Validator indices to include in the result const inactivityPenaltyDenominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR; for (let i = 0; i < statuses.length; i++) { - if (validatorIndices !== undefined && !validatorIndices.includes(i)) { + if (validatorIndices.length && !validatorIndices.includes(i)) { continue; } diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index ba45d03adbab..7922e20c2317 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -9,7 +9,7 @@ type BalanceRecord = {val: number}; // Use val for convenient way to increment/d export async function computeSyncCommitteeRewards( block: allForks.BeaconBlock, preState: CachedBeaconStateAllForks, - validatorIds?: (ValidatorIndex | string)[] + validatorIds: (ValidatorIndex | string)[] = [] ): Promise { const fork = preState.config.getForkName(block.slot); if (fork === ForkName.phase0) { @@ -46,7 +46,7 @@ export async function computeSyncCommitteeRewards( const rewards = Array.from(balances, ([validatorIndex, v]) => ({validatorIndex, reward: v.val})); - if (validatorIds !== undefined) { + if (validatorIds.length) { const filtersSet = new Set(validatorIds); return rewards.filter( (reward) => filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex()) From d101913ca37faa32e5244a86edf4c8f772f033e8 Mon Sep 17 00:00:00 2001 From: g11tech Date: Fri, 14 Jun 2024 16:29:18 +0530 Subject: [PATCH 21/55] feat: generalize blobs to data for extension ready for ils and/or data columns (#6693) rename types and fix references to them update forkchoice and availability flow propagate changes to the codebase lint and tsc some fixes cleanup test run fixes fix the tests fix apply feedback --- .../src/api/impl/beacon/blocks/index.ts | 23 +++- .../src/api/impl/validator/index.ts | 22 ++- .../src/chain/blocks/importBlock.ts | 15 ++- .../beacon-node/src/chain/blocks/index.ts | 3 +- .../beacon-node/src/chain/blocks/types.ts | 97 +++++++++----- .../src/chain/blocks/verifyBlock.ts | 6 +- .../blocks/verifyBlocksDataAvailability.ts | 42 +++--- .../src/chain/blocks/writeBlockInputToDb.ts | 12 +- .../beacon-node/src/chain/forkChoice/index.ts | 3 + .../chain/seenCache/seenGossipBlockInput.ts | 87 ++++++------ .../src/network/processor/gossipHandlers.ts | 4 +- .../reqresp/beaconBlocksMaybeBlobsByRange.ts | 25 ++-- .../reqresp/beaconBlocksMaybeBlobsByRoot.ts | 21 +-- packages/beacon-node/src/sync/interface.ts | 2 +- packages/beacon-node/src/sync/range/chain.ts | 4 +- packages/beacon-node/src/sync/unknownBlock.ts | 6 +- .../src/sync/utils/pendingBlocksTree.ts | 2 +- .../onWorker/dataSerialization.test.ts | 22 +-- .../test/e2e/sync/unknownBlockSync.test.ts | 2 +- .../test/mocks/mockedBeaconChain.ts | 1 + .../opPools/aggregatedAttestationPool.test.ts | 4 +- .../perf/chain/verifyImportBlocks.test.ts | 2 +- .../test/spec/presets/fork_choice.test.ts | 19 ++- .../api/impl/validator/produceBlockV2.test.ts | 1 + .../api/impl/validator/produceBlockV3.test.ts | 2 + .../blocks/verifyBlocksSanityChecks.test.ts | 2 +- .../unit/chain/forkChoice/forkChoice.test.ts | 125 +++++++++++++++--- .../seenCache/seenGossipBlockInput.test.ts | 4 +- .../beaconBlocksMaybeBlobsByRange.test.ts | 12 +- .../test/unit/sync/range/batch.test.ts | 2 +- .../test/unit/sync/range/chain.test.ts | 4 +- .../test/unit/sync/unknownBlock.test.ts | 2 +- packages/beacon-node/test/utils/state.ts | 3 +- .../beacon-node/test/utils/typeGenerator.ts | 4 +- .../test/utils/validationData/attestation.ts | 3 +- .../fork-choice/src/forkChoice/forkChoice.ts | 19 ++- .../fork-choice/src/forkChoice/interface.ts | 11 +- packages/fork-choice/src/index.ts | 3 +- .../fork-choice/src/protoArray/interface.ts | 19 ++- .../fork-choice/test/perf/forkChoice/util.ts | 11 +- .../test/unit/forkChoice/forkChoice.test.ts | 3 + .../unit/forkChoice/getProposerHead.test.ts | 12 +- .../protoArray/executionStatusUpdates.test.ts | 12 +- .../unit/protoArray/getCommonAncestor.test.ts | 4 +- .../test/unit/protoArray/protoArray.test.ts | 5 +- 45 files changed, 458 insertions(+), 229 deletions(-) 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 c7c34d45971a..613ffeaacfdd 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -4,7 +4,14 @@ import {computeEpochAtSlot, computeTimeAtSlot, reconstructFullBlockOrContents} f import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; import {sleep, fromHex, toHex} from "@lodestar/utils"; import {allForks, deneb, isSignedBlockContents, ProducedBlockSource} from "@lodestar/types"; -import {BlockSource, getBlockInput, ImportBlockOpts, BlockInput, BlobsSource} from "../../../../chain/blocks/types.js"; +import { + BlockSource, + getBlockInput, + ImportBlockOpts, + BlockInput, + BlobsSource, + BlockInputDataBlobs, +} from "../../../../chain/blocks/types.js"; import {promiseAllMaybeAsync} from "../../../../util/promises.js"; import {isOptimisticBlock} from "../../../../util/forkChoice.js"; import {computeBlobSidecars} from "../../../../util/blobs.js"; @@ -51,20 +58,24 @@ export function getBeaconBlockApi({ if (isSignedBlockContents(signedBlockOrContents)) { ({signedBlock} = signedBlockOrContents); blobSidecars = computeBlobSidecars(config, signedBlock, signedBlockOrContents); - blockForImport = getBlockInput.postDeneb( + const blockData = { + fork: config.getForkName(signedBlock.message.slot), + blobs: blobSidecars, + blobsSource: BlobsSource.api, + blobsBytes: blobSidecars.map(() => null), + } as BlockInputDataBlobs; + blockForImport = getBlockInput.availableData( config, signedBlock, BlockSource.api, - blobSidecars, - BlobsSource.api, // don't bundle any bytes for block and blobs null, - blobSidecars.map(() => null) + blockData ); } else { signedBlock = signedBlockOrContents; blobSidecars = []; - blockForImport = getBlockInput.preDeneb(config, signedBlock, BlockSource.api, context?.sszBytes ?? null); + blockForImport = getBlockInput.preData(config, signedBlock, BlockSource.api, context?.sszBytes ?? null); } // check what validations have been requested before broadcasting and publishing the block diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 0b8f4d9cce36..2c0958de5d5a 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -38,7 +38,7 @@ import { phase0, Wei, } from "@lodestar/types"; -import {ExecutionStatus} from "@lodestar/fork-choice"; +import {ExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {fromHex, toHex, resolveOrRacePromises, prettyWeiToEth} from "@lodestar/utils"; import { AttestationError, @@ -324,7 +324,7 @@ export function getValidatorApi({ function notOnOptimisticBlockRoot(beaconBlockRoot: Root): void { const protoBeaconBlock = chain.forkChoice.getBlock(beaconBlockRoot); if (!protoBeaconBlock) { - throw new ApiError(400, "Block not in forkChoice"); + throw new ApiError(400, `Block not in forkChoice, beaconBlockRoot=${toHex(beaconBlockRoot)}`); } if (protoBeaconBlock.executionStatus === ExecutionStatus.Syncing) @@ -333,6 +333,16 @@ export function getValidatorApi({ ); } + function notOnOutOfRangeData(beaconBlockRoot: Root): void { + const protoBeaconBlock = chain.forkChoice.getBlock(beaconBlockRoot); + if (!protoBeaconBlock) { + throw new ApiError(400, `Block not in forkChoice, beaconBlockRoot=${toHex(beaconBlockRoot)}`); + } + + if (protoBeaconBlock.dataAvailabilityStatus === DataAvailabilityStatus.OutOfRange) + throw new NodeIsSyncing("Block's data is out of range and not validated"); + } + async function produceBuilderBlindedBlock( slot: Slot, randaoReveal: BLSSignature, @@ -385,6 +395,7 @@ export function getValidatorApi({ } else { parentBlockRoot = inParentBlockRoot; } + notOnOutOfRangeData(parentBlockRoot); let timer; try { @@ -452,6 +463,7 @@ export function getValidatorApi({ } else { parentBlockRoot = inParentBlockRoot; } + notOnOutOfRangeData(parentBlockRoot); let timer; try { @@ -522,6 +534,7 @@ export function getValidatorApi({ // handler, in which case this should just return. chain.forkChoice.updateTime(slot); const parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot); + notOnOutOfRangeData(parentBlockRoot); const fork = config.getForkName(slot); // set some sensible opts @@ -825,11 +838,15 @@ export function getValidatorApi({ // Check the execution status as validator shouldn't vote on an optimistic head // Check on target is sufficient as a valid target would imply a valid source notOnOptimisticBlockRoot(targetRoot); + notOnOutOfRangeData(targetRoot); // To get the correct source we must get a state in the same epoch as the attestation's epoch. // An epoch transition may change state.currentJustifiedCheckpoint const attEpochState = await chain.getHeadStateAtEpoch(attEpoch, RegenCaller.produceAttestationData); + // TODO confirm if the below is correct assertion + // notOnOutOfRangeData(attEpochState.currentJustifiedCheckpoint.root); + return { data: { slot, @@ -865,6 +882,7 @@ export function getValidatorApi({ // Check the execution status as validator shouldn't contribute on an optimistic head notOnOptimisticBlockRoot(beaconBlockRoot); + notOnOutOfRangeData(beaconBlockRoot); const contribution = chain.syncCommitteeMessagePool.getContribution(subcommitteeIndex, slot, beaconBlockRoot); if (!contribution) throw new ApiError(500, "No contribution available"); diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index 4d0dcb341481..9c467c26ca50 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -58,7 +58,7 @@ export async function importBlock( fullyVerifiedBlock: FullyVerifiedBlock, opts: ImportBlockOpts ): Promise { - const {blockInput, postState, parentBlockSlot, executionStatus} = fullyVerifiedBlock; + const {blockInput, postState, parentBlockSlot, executionStatus, dataAvailabilityStatus} = fullyVerifiedBlock; const {block, source} = blockInput; const {slot: blockSlot} = block.message; const blockRoot = this.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block.message); @@ -70,8 +70,8 @@ export async function importBlock( const blockDelaySec = (fullyVerifiedBlock.seenTimestampSec - postState.genesisTime) % this.config.SECONDS_PER_SLOT; const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000); - // this is just a type assertion since blockinput with blobsPromise type will not end up here - if (blockInput.type === BlockInputType.blobsPromise) { + // this is just a type assertion since blockinput with dataPromise type will not end up here + if (blockInput.type === BlockInputType.dataPromise) { throw Error("Unavailable block can not be imported in forkchoice"); } @@ -90,7 +90,8 @@ export async function importBlock( postState, blockDelaySec, this.clock.currentSlot, - executionStatus + executionStatus, + dataAvailabilityStatus ); // This adds the state necessary to process the next block @@ -108,11 +109,11 @@ export async function importBlock( executionOptimistic: blockSummary != null && isOptimisticBlock(blockSummary), }); - // blobsPromise will not end up here, but preDeneb could. In future we might also allow syncing + // 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.postDeneb) { - const {blobsSource, blobs} = blockInput; + if (blockInput.type === BlockInputType.availableData) { + const {blobsSource, blobs} = blockInput.blockData; this.metrics?.importBlock.blobsBySource.inc({blobsSource}); for (const blobSidecar of blobs) { diff --git a/packages/beacon-node/src/chain/blocks/index.ts b/packages/beacon-node/src/chain/blocks/index.ts index 12450fe85f94..083ab6bd4471 100644 --- a/packages/beacon-node/src/chain/blocks/index.ts +++ b/packages/beacon-node/src/chain/blocks/index.ts @@ -87,9 +87,8 @@ export async function processBlocks( postState: postStates[i], parentBlockSlot: parentSlots[i], executionStatus: executionStatuses[i], - // Currently dataAvailableStatus is not used upstream but that can change if we // start supporting optimistic syncing/processing - dataAvailableStatus: dataAvailabilityStatuses[i], + dataAvailabilityStatus: dataAvailabilityStatuses[i], proposerBalanceDelta: proposerBalanceDeltas[i], // TODO: Make this param mandatory and capture in gossip seenTimestampSec: opts.seenTimestampSec ?? Math.floor(Date.now() / 1000), diff --git a/packages/beacon-node/src/chain/blocks/types.ts b/packages/beacon-node/src/chain/blocks/types.ts index 170ebd8a5a0c..2996bac7887f 100644 --- a/packages/beacon-node/src/chain/blocks/types.ts +++ b/packages/beacon-node/src/chain/blocks/types.ts @@ -1,13 +1,16 @@ -import {CachedBeaconStateAllForks, computeEpochAtSlot, DataAvailableStatus} from "@lodestar/state-transition"; -import {MaybeValidExecutionStatus} from "@lodestar/fork-choice"; +import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition"; +import {MaybeValidExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {allForks, deneb, Slot, RootHex} from "@lodestar/types"; -import {ForkSeq} from "@lodestar/params"; +import {ForkSeq, ForkName} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; export enum BlockInputType { - preDeneb = "preDeneb", - postDeneb = "postDeneb", - blobsPromise = "blobsPromise", + // preData is preDeneb + preData = "preData", + // data is out of available window, can be used to sync forward and keep adding to forkchoice + outOfRangeData = "outOfRangeData", + availableData = "availableData", + dataPromise = "dataPromise", } /** Enum to represent where blocks come from */ @@ -31,21 +34,28 @@ export enum GossipedInputType { blob = "blob", } -export type BlobsCache = Map; +type BlobsCacheMap = Map; + +type ForkBlobsInfo = {fork: ForkName.deneb}; +type BlobsData = {blobs: deneb.BlobSidecars; blobsBytes: (Uint8Array | null)[]; blobsSource: BlobsSource}; +export type BlockInputDataBlobs = ForkBlobsInfo & BlobsData; +export type BlockInputData = BlockInputDataBlobs; + export type BlockInputBlobs = {blobs: deneb.BlobSidecars; blobsBytes: (Uint8Array | null)[]; blobsSource: BlobsSource}; -type CachedBlobs = { - blobsCache: BlobsCache; - availabilityPromise: Promise; - resolveAvailability: (blobs: BlockInputBlobs) => void; -}; +type Availability = {availabilityPromise: Promise; resolveAvailability: (data: T) => void}; + +type CachedBlobs = {blobsCache: BlobsCacheMap} & Availability; +export type CachedData = ForkBlobsInfo & CachedBlobs; export type BlockInput = {block: allForks.SignedBeaconBlock; source: BlockSource; blockBytes: Uint8Array | null} & ( - | {type: BlockInputType.preDeneb} - | ({type: BlockInputType.postDeneb} & BlockInputBlobs) + | {type: BlockInputType.preData | BlockInputType.outOfRangeData} + | ({type: BlockInputType.availableData} & {blockData: BlockInputData}) // the blobsSource here is added to BlockInputBlobs when availability is resolved - | ({type: BlockInputType.blobsPromise} & CachedBlobs) + | ({type: BlockInputType.dataPromise} & {cachedData: CachedData}) ); -export type NullBlockInput = {block: null; blockRootHex: RootHex; blockInputPromise: Promise} & CachedBlobs; +export type NullBlockInput = {block: null; blockRootHex: RootHex; blockInputPromise: Promise} & { + cachedData: CachedData; +}; export function blockRequiresBlobs(config: ChainForkConfig, blockSlot: Slot, clockSlot: Slot): boolean { return ( @@ -56,7 +66,7 @@ export function blockRequiresBlobs(config: ChainForkConfig, blockSlot: Slot, clo } export const getBlockInput = { - preDeneb( + preData( config: ChainForkConfig, block: allForks.SignedBeaconBlock, source: BlockSource, @@ -66,61 +76,76 @@ export const getBlockInput = { throw Error(`Post Deneb block slot ${block.message.slot}`); } return { - type: BlockInputType.preDeneb, + type: BlockInputType.preData, + block, + source, + blockBytes, + }; + }, + + // This isn't used right now but we might enable importing blobs into forkchoice from a point + // where data is not guaranteed to be available to hopefully reach a point where we have + // available data. Hence the validator duties can't be performed on outOfRangeData + // + // This can help with some of the requests of syncing without data for some use cases for e.g. + // building states or where importing data isn't important if valid child exists like ILs + outOfRangeData( + config: ChainForkConfig, + block: allForks.SignedBeaconBlock, + source: BlockSource, + blockBytes: Uint8Array | null + ): BlockInput { + if (config.getForkSeq(block.message.slot) < ForkSeq.deneb) { + throw Error(`Pre Deneb block slot ${block.message.slot}`); + } + return { + type: BlockInputType.outOfRangeData, block, source, blockBytes, }; }, - postDeneb( + availableData( config: ChainForkConfig, block: allForks.SignedBeaconBlock, source: BlockSource, - blobs: deneb.BlobSidecars, - blobsSource: BlobsSource, blockBytes: Uint8Array | null, - blobsBytes: (Uint8Array | null)[] + blockData: BlockInputData ): BlockInput { if (config.getForkSeq(block.message.slot) < ForkSeq.deneb) { throw Error(`Pre Deneb block slot ${block.message.slot}`); } return { - type: BlockInputType.postDeneb, + type: BlockInputType.availableData, block, source, - blobs, - blobsSource, blockBytes, - blobsBytes, + blockData, }; }, - blobsPromise( + dataPromise( config: ChainForkConfig, block: allForks.SignedBeaconBlock, source: BlockSource, - blobsCache: BlobsCache, blockBytes: Uint8Array | null, - availabilityPromise: Promise, - resolveAvailability: (blobs: BlockInputBlobs) => void + cachedData: CachedData ): BlockInput { if (config.getForkSeq(block.message.slot) < ForkSeq.deneb) { throw Error(`Pre Deneb block slot ${block.message.slot}`); } return { - type: BlockInputType.blobsPromise, + type: BlockInputType.dataPromise, block, source, - blobsCache, blockBytes, - availabilityPromise, - resolveAvailability, + cachedData, }; }, }; -export function getBlockInputBlobs(blobsCache: BlobsCache): Omit { +export function getBlockInputBlobs(blobsCache: BlobsCacheMap): Omit { const blobs = []; const blobsBytes = []; @@ -206,7 +231,7 @@ export type FullyVerifiedBlock = { * used in optimistic sync or for merge block */ executionStatus: MaybeValidExecutionStatus; - dataAvailableStatus: DataAvailableStatus; + dataAvailabilityStatus: DataAvailabilityStatus; /** Seen timestamp seconds */ seenTimestampSec: number; }; diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index 9bb7192b2820..bf4cc7e60dcd 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -7,7 +7,7 @@ import { } from "@lodestar/state-transition"; import {bellatrix, deneb} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; -import {ProtoBlock, ExecutionStatus} from "@lodestar/fork-choice"; +import {ProtoBlock, ExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {ChainForkConfig} from "@lodestar/config"; import {Logger} from "@lodestar/utils"; import {BlockError, BlockErrorCode} from "../errors/index.js"; @@ -44,7 +44,7 @@ export async function verifyBlocksInEpoch( postStates: CachedBeaconStateAllForks[]; proposerBalanceDeltas: number[]; segmentExecStatus: SegmentExecStatus; - dataAvailabilityStatuses: DataAvailableStatus[]; + dataAvailabilityStatuses: DataAvailabilityStatus[]; availableBlockInputs: BlockInput[]; }> { const blocks = blocksInput.map(({block}) => block); @@ -169,7 +169,7 @@ export async function verifyBlocksInEpoch( blocksInput.length === 1 && // gossip blocks have seenTimestampSec opts.seenTimestampSec !== undefined && - blocksInput[0].type !== BlockInputType.preDeneb && + blocksInput[0].type !== BlockInputType.preData && executionStatuses[0] === ExecutionStatus.Valid ) { // Find the max time when the block was actually verified diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksDataAvailability.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksDataAvailability.ts index f995dde967bd..8393c91063de 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksDataAvailability.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksDataAvailability.ts @@ -1,4 +1,5 @@ -import {computeTimeAtSlot, DataAvailableStatus} from "@lodestar/state-transition"; +import {computeTimeAtSlot} from "@lodestar/state-transition"; +import {DataAvailabilityStatus} from "@lodestar/fork-choice"; import {ChainForkConfig} from "@lodestar/config"; import {deneb, UintNum64} from "@lodestar/types"; import {Logger} from "@lodestar/utils"; @@ -28,7 +29,7 @@ export async function verifyBlocksDataAvailability( blocks: BlockInput[], opts: ImportBlockOpts ): Promise<{ - dataAvailabilityStatuses: DataAvailableStatus[]; + dataAvailabilityStatuses: DataAvailabilityStatus[]; availableTime: number; availableBlockInputs: BlockInput[]; }> { @@ -36,7 +37,7 @@ export async function verifyBlocksDataAvailability( throw Error("Empty partiallyVerifiedBlocks"); } - const dataAvailabilityStatuses: DataAvailableStatus[] = []; + const dataAvailabilityStatuses: DataAvailabilityStatus[] = []; const seenTime = opts.seenTimestampSec !== undefined ? opts.seenTimestampSec * 1000 : Date.now(); const availableBlockInputs: BlockInput[] = []; @@ -49,8 +50,8 @@ export async function verifyBlocksDataAvailability( availableBlockInputs.push(availableBlockInput); } - const availableTime = blocks[blocks.length - 1].type === BlockInputType.blobsPromise ? Date.now() : seenTime; - if (blocks.length === 1 && opts.seenTimestampSec !== undefined && blocks[0].type !== BlockInputType.preDeneb) { + const availableTime = blocks[blocks.length - 1].type === BlockInputType.dataPromise ? Date.now() : seenTime; + if (blocks.length === 1 && opts.seenTimestampSec !== undefined && blocks[0].type !== BlockInputType.preData) { const recvToAvailableTime = availableTime / 1000 - opts.seenTimestampSec; const numBlobs = (blocks[0].block as deneb.SignedBeaconBlock).message.body.blobKzgCommitments.length; @@ -69,27 +70,30 @@ async function maybeValidateBlobs( chain: {config: ChainForkConfig; genesisTime: UintNum64; logger: Logger}, blockInput: BlockInput, opts: ImportBlockOpts -): Promise<{dataAvailabilityStatus: DataAvailableStatus; availableBlockInput: BlockInput}> { +): Promise<{dataAvailabilityStatus: DataAvailabilityStatus; availableBlockInput: BlockInput}> { switch (blockInput.type) { - case BlockInputType.preDeneb: - return {dataAvailabilityStatus: DataAvailableStatus.preDeneb, availableBlockInput: blockInput}; + case BlockInputType.preData: + return {dataAvailabilityStatus: DataAvailabilityStatus.PreData, availableBlockInput: blockInput}; - case BlockInputType.postDeneb: + case BlockInputType.outOfRangeData: + return {dataAvailabilityStatus: DataAvailabilityStatus.OutOfRange, availableBlockInput: blockInput}; + + case BlockInputType.availableData: if (opts.validBlobSidecars === BlobSidecarValidation.Full) { - return {dataAvailabilityStatus: DataAvailableStatus.available, availableBlockInput: blockInput}; + return {dataAvailabilityStatus: DataAvailabilityStatus.Available, availableBlockInput: blockInput}; } // eslint-disable-next-line no-fallthrough - case BlockInputType.blobsPromise: { + case BlockInputType.dataPromise: { // run full validation const {block} = blockInput; const blockSlot = block.message.slot; const blobsData = - blockInput.type === BlockInputType.postDeneb - ? blockInput - : await raceWithCutoff(chain, blockInput, blockInput.availabilityPromise); - const {blobs, blobsBytes, blobsSource} = blobsData; + blockInput.type === BlockInputType.availableData + ? blockInput.blockData + : await raceWithCutoff(chain, blockInput, blockInput.cachedData.availabilityPromise); + const {blobs} = blobsData; const {blobKzgCommitments} = (block as deneb.SignedBeaconBlock).message.body; const beaconBlockRoot = chain.config.getForkTypes(blockSlot).BeaconBlock.hashTreeRoot(block.message); @@ -99,16 +103,14 @@ async function maybeValidateBlobs( const skipProofsCheck = opts.validBlobSidecars === BlobSidecarValidation.Individual; validateBlobSidecars(blockSlot, beaconBlockRoot, blobKzgCommitments, blobs, {skipProofsCheck}); - const availableBlockInput = getBlockInput.postDeneb( + const availableBlockInput = getBlockInput.availableData( chain.config, blockInput.block, blockInput.source, - blobs, - blobsSource, blockInput.blockBytes, - blobsBytes + blobsData ); - return {dataAvailabilityStatus: DataAvailableStatus.available, availableBlockInput: availableBlockInput}; + return {dataAvailabilityStatus: DataAvailabilityStatus.Available, availableBlockInput: availableBlockInput}; } } } diff --git a/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts b/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts index 0b94d32b84ec..b0f5ab159591 100644 --- a/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts +++ b/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts @@ -29,12 +29,12 @@ export async function writeBlockInputToDb(this: BeaconChain, blocksInput: BlockI root: blockRootHex, }); - if (blockInput.type === BlockInputType.postDeneb || blockInput.type === BlockInputType.blobsPromise) { + if (blockInput.type === BlockInputType.availableData || blockInput.type === BlockInputType.dataPromise) { const blobSidecars = - blockInput.type == BlockInputType.postDeneb - ? blockInput.blobs + blockInput.type == BlockInputType.availableData + ? blockInput.blockData.blobs : // At this point of import blobs are available and can be safely awaited - (await blockInput.availabilityPromise).blobs; + (await blockInput.cachedData.availabilityPromise).blobs; // NOTE: Old blobs are pruned on archive fnPromises.push(this.db.blobSidecars.add({blockRoot, slot: block.message.slot, blobSidecars})); @@ -63,8 +63,8 @@ export async function removeEagerlyPersistedBlockInputs(this: BeaconChain, block if (!this.forkChoice.hasBlockHex(blockRootHex)) { blockToRemove.push(block); - if (type === BlockInputType.postDeneb) { - const blobSidecars = blockInput.blobs; + if (type === BlockInputType.availableData) { + const blobSidecars = blockInput.blockData.blobs; blobsToRemove.push({blockRoot, slot: block.message.slot, blobSidecars}); } } diff --git a/packages/beacon-node/src/chain/forkChoice/index.ts b/packages/beacon-node/src/chain/forkChoice/index.ts index a9e728a0b3a8..9d3b9b03f706 100644 --- a/packages/beacon-node/src/chain/forkChoice/index.ts +++ b/packages/beacon-node/src/chain/forkChoice/index.ts @@ -8,6 +8,7 @@ import { ExecutionStatus, JustifiedBalancesGetter, ForkChoiceOpts as RawForkChoiceOpts, + DataAvailabilityStatus, } from "@lodestar/fork-choice"; import { CachedBeaconStateAllForks, @@ -95,6 +96,8 @@ export function initializeForkChoice( executionStatus: blockHeader.slot === GENESIS_SLOT ? ExecutionStatus.Valid : ExecutionStatus.Syncing, } : {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}), + + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, currentSlot ), diff --git a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts index 77976b70e6e8..7e8d8a7ebcbc 100644 --- a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts +++ b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts @@ -2,15 +2,15 @@ import {toHexString} from "@chainsafe/ssz"; import {deneb, RootHex, ssz, allForks} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {pruneSetToMax} from "@lodestar/utils"; -import {BLOBSIDECAR_FIXED_SIZE, ForkSeq} from "@lodestar/params"; +import {BLOBSIDECAR_FIXED_SIZE, isForkBlobs, ForkName} from "@lodestar/params"; import { BlockInput, NullBlockInput, getBlockInput, BlockSource, - BlockInputBlobs, - BlobsCache, + BlockInputDataBlobs, + CachedData, GossipedInputType, getBlockInputBlobs, BlobsSource, @@ -27,12 +27,10 @@ type GossipedBlockInput = | {type: GossipedInputType.blob; blobSidecar: deneb.BlobSidecar; blobBytes: Uint8Array | null}; type BlockInputCacheType = { + fork: ForkName; block?: allForks.SignedBeaconBlock; blockBytes?: Uint8Array | null; - blobsCache: BlobsCache; - // blobs promise and its callback cached for delayed resolution - availabilityPromise: Promise; - resolveAvailability: (blobs: BlockInputBlobs) => void; + cachedData?: CachedData; // block promise and its callback cached for delayed resolution blockInputPromise: Promise; resolveBlockInput: (blockInput: BlockInput) => void; @@ -77,25 +75,29 @@ export class SeenGossipBlockInput { } { let blockHex; let blockCache; + let fork; if (gossipedInput.type === GossipedInputType.block) { const {signedBlock, blockBytes} = gossipedInput; + fork = config.getForkName(signedBlock.message.slot); blockHex = toHexString( config.getForkTypes(signedBlock.message.slot).BeaconBlock.hashTreeRoot(signedBlock.message) ); - blockCache = this.blockInputCache.get(blockHex) ?? getEmptyBlockInputCacheEntry(); + blockCache = this.blockInputCache.get(blockHex) ?? getEmptyBlockInputCacheEntry(fork); blockCache.block = signedBlock; blockCache.blockBytes = blockBytes; } else { const {blobSidecar, blobBytes} = gossipedInput; const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blobSidecar.signedBlockHeader.message); + fork = config.getForkName(blobSidecar.signedBlockHeader.message.slot); + blockHex = toHexString(blockRoot); - blockCache = this.blockInputCache.get(blockHex) ?? getEmptyBlockInputCacheEntry(); + blockCache = this.blockInputCache.get(blockHex) ?? getEmptyBlockInputCacheEntry(fork); // TODO: freetheblobs check if its the same blob or a duplicate and throw/take actions - blockCache.blobsCache.set(blobSidecar.index, { + blockCache.cachedData?.blobsCache.set(blobSidecar.index, { blobSidecar, // easily splice out the unsigned message as blob is a fixed length type blobBytes: blobBytes?.slice(0, BLOBSIDECAR_FIXED_SIZE) ?? null, @@ -106,23 +108,21 @@ export class SeenGossipBlockInput { this.blockInputCache.set(blockHex, blockCache); } - const { - block: signedBlock, - blockBytes, - blobsCache, - availabilityPromise, - resolveAvailability, - blockInputPromise, - resolveBlockInput, - } = blockCache; + const {block: signedBlock, blockBytes, blockInputPromise, resolveBlockInput, cachedData} = blockCache; if (signedBlock !== undefined) { - if (config.getForkSeq(signedBlock.message.slot) < ForkSeq.deneb) { + if (!isForkBlobs(fork)) { return { - blockInput: getBlockInput.preDeneb(config, signedBlock, BlockSource.gossip, blockBytes ?? null), + blockInput: getBlockInput.preData(config, signedBlock, BlockSource.gossip, blockBytes ?? null), blockInputMeta: {pending: null, haveBlobs: 0, expectedBlobs: 0}, }; } + + if (cachedData === undefined || !isForkBlobs(cachedData.fork)) { + throw Error("Missing or Invalid fork cached Data for deneb+ block"); + } + const {blobsCache, resolveAvailability} = cachedData; + // block is available, check if all blobs have shown up const {slot, body} = signedBlock.message; const {blobKzgCommitments} = body as deneb.BeaconBlockBody; @@ -136,33 +136,29 @@ export class SeenGossipBlockInput { if (blobKzgCommitments.length === blobsCache.size) { const allBlobs = getBlockInputBlobs(blobsCache); - resolveAvailability({...allBlobs, blobsSource: BlobsSource.gossip}); + const blockData = {...allBlobs, blobsSource: BlobsSource.gossip, fork: cachedData.fork}; + resolveAvailability(blockData); metrics?.syncUnknownBlock.resolveAvailabilitySource.inc({source: BlockInputAvailabilitySource.GOSSIP}); - const {blobs, blobsBytes} = allBlobs; - const blockInput = getBlockInput.postDeneb( + const blockInput = getBlockInput.availableData( config, signedBlock, BlockSource.gossip, - blobs, - BlobsSource.gossip, blockBytes ?? null, - blobsBytes + blockData ); resolveBlockInput(blockInput); return { blockInput, - blockInputMeta: {pending: null, haveBlobs: blobs.length, expectedBlobs: blobKzgCommitments.length}, + blockInputMeta: {pending: null, haveBlobs: allBlobs.blobs.length, expectedBlobs: blobKzgCommitments.length}, }; } else { - const blockInput = getBlockInput.blobsPromise( + const blockInput = getBlockInput.dataPromise( config, signedBlock, BlockSource.gossip, - blobsCache, blockBytes ?? null, - availabilityPromise, - resolveAvailability + cachedData ); resolveBlockInput(blockInput); @@ -177,13 +173,16 @@ export class SeenGossipBlockInput { } } else { // will need to wait for the block to showup + if (cachedData === undefined) { + throw Error("Missing cachedData for deneb+ blobs"); + } + const {blobsCache} = cachedData; + return { blockInput: { block: null, blockRootHex: blockHex, - blobsCache, - availabilityPromise, - resolveAvailability, + cachedData, blockInputPromise, }, blockInputMeta: {pending: GossipedInputType.block, haveBlobs: blobsCache.size, expectedBlobs: null}, @@ -192,22 +191,30 @@ export class SeenGossipBlockInput { } } -function getEmptyBlockInputCacheEntry(): BlockInputCacheType { +function getEmptyBlockInputCacheEntry(fork: ForkName): BlockInputCacheType { // Capture both the promise and its callbacks for blockInput and final availability // It is not spec'ed but in tests in Firefox and NodeJS the promise constructor is run immediately let resolveBlockInput: ((block: BlockInput) => void) | null = null; const blockInputPromise = new Promise((resolveCB) => { resolveBlockInput = resolveCB; }); + if (resolveBlockInput === null) { + throw Error("Promise Constructor was not executed immediately"); + } + if (!isForkBlobs(fork)) { + return {fork, blockInputPromise, resolveBlockInput}; + } - let resolveAvailability: ((blobs: BlockInputBlobs) => void) | null = null; - const availabilityPromise = new Promise((resolveCB) => { + let resolveAvailability: ((blobs: BlockInputDataBlobs) => void) | null = null; + const availabilityPromise = new Promise((resolveCB) => { resolveAvailability = resolveCB; }); - if (resolveAvailability === null || resolveBlockInput === null) { + if (resolveAvailability === null) { throw Error("Promise Constructor was not executed immediately"); } + const blobsCache = new Map(); - return {blockInputPromise, resolveBlockInput, availabilityPromise, resolveAvailability, blobsCache}; + const cachedData: CachedData = {fork, blobsCache, availabilityPromise, resolveAvailability}; + return {fork, blockInputPromise, resolveBlockInput, cachedData}; } diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 519e314df055..1a71cc7de334 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -259,7 +259,7 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler metrics?.registerBeaconBlock(OpSource.gossip, seenTimestampSec, signedBlock.message); // if blobs are not yet fully available start an aggressive blob pull - if (blockInput.type === BlockInputType.blobsPromise) { + if (blockInput.type === BlockInputType.dataPromise) { events.emit(NetworkEvent.unknownBlockInput, {blockInput, peer: peerIdStr}); } @@ -398,7 +398,7 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler // // however we can emit the event which will atleast add the peer to the list of peers to pull // data from - if (normalBlockInput.type === BlockInputType.blobsPromise) { + if (normalBlockInput.type === BlockInputType.dataPromise) { events.emit(NetworkEvent.unknownBlockInput, {blockInput: normalBlockInput, peer: peerIdStr}); } } else { diff --git a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts index 2d66584dd716..ff5689a7b8c3 100644 --- a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts +++ b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts @@ -3,7 +3,7 @@ import {deneb, Epoch, phase0, allForks, Slot} from "@lodestar/types"; import {ForkSeq} from "@lodestar/params"; import {computeEpochAtSlot} from "@lodestar/state-transition"; -import {BlobsSource, BlockInput, BlockSource, getBlockInput} from "../../chain/blocks/types.js"; +import {BlobsSource, BlockInput, BlockSource, getBlockInput, BlockInputDataBlobs} from "../../chain/blocks/types.js"; import {PeerIdStr} from "../../util/peerId.js"; import {INetwork, WithBytes} from "../interface.js"; @@ -33,7 +33,7 @@ export async function beaconBlocksMaybeBlobsByRange( // Note: Assumes all blocks in the same epoch if (config.getForkSeq(startSlot) < ForkSeq.deneb) { const blocks = await network.sendBeaconBlocksByRange(peerId, request); - return blocks.map((block) => getBlockInput.preDeneb(config, block.data, BlockSource.byRange, block.bytes)); + return blocks.map((block) => getBlockInput.preData(config, block.data, BlockSource.byRange, block.bytes)); } // Only request blobs if they are recent enough @@ -74,7 +74,7 @@ export function matchBlockWithBlobs( for (let i = 0; i < allBlocks.length; i++) { const block = allBlocks[i]; if (config.getForkSeq(block.data.message.slot) < ForkSeq.deneb) { - blockInputs.push(getBlockInput.preDeneb(config, block.data, blockSource, block.bytes)); + blockInputs.push(getBlockInput.preData(config, block.data, blockSource, block.bytes)); } else { const blobSidecars: deneb.BlobSidecar[] = []; @@ -95,18 +95,15 @@ export function matchBlockWithBlobs( ); } + const blockData = { + fork: config.getForkName(block.data.message.slot), + blobs: blobSidecars, + blobsSource, + blobsBytes: Array.from({length: blobKzgCommitmentsLen}, () => null), + } as BlockInputDataBlobs; + // TODO DENEB: instead of null, pass payload in bytes - blockInputs.push( - getBlockInput.postDeneb( - config, - block.data, - blockSource, - blobSidecars, - blobsSource, - null, - Array.from({length: blobKzgCommitmentsLen}, () => null) - ) - ); + blockInputs.push(getBlockInput.availableData(config, block.data, blockSource, null, blockData)); } } diff --git a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts index 95d88f39586f..2b802ab1edd9 100644 --- a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts +++ b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts @@ -10,6 +10,7 @@ import { getBlockInput, NullBlockInput, BlobsSource, + BlockInputDataBlobs, } from "../../chain/blocks/types.js"; import {PeerIdStr} from "../../util/peerId.js"; import {INetwork} from "../interface.js"; @@ -58,19 +59,21 @@ export async function unavailableBeaconBlobsByRoot( unavailableBlockInput: BlockInput | NullBlockInput, metrics: Metrics | null ): Promise { - if (unavailableBlockInput.block !== null && unavailableBlockInput.type !== BlockInputType.blobsPromise) { - return unavailableBlockInput as BlockInput; + if (unavailableBlockInput.block !== null && unavailableBlockInput.type !== BlockInputType.dataPromise) { + return unavailableBlockInput; } // resolve the block if thats unavailable - let block, blobsCache, blockBytes, resolveAvailability; + let block, blobsCache, blockBytes, resolveAvailability, cachedData; if (unavailableBlockInput.block === null) { const allBlocks = await network.sendBeaconBlocksByRoot(peerId, [fromHexString(unavailableBlockInput.blockRootHex)]); block = allBlocks[0].data; blockBytes = allBlocks[0].bytes; - ({blobsCache, resolveAvailability} = unavailableBlockInput); + cachedData = unavailableBlockInput.cachedData; + ({blobsCache, resolveAvailability} = cachedData); } else { - ({block, blobsCache, resolveAvailability, blockBytes} = unavailableBlockInput); + ({block, cachedData, blockBytes} = unavailableBlockInput); + ({blobsCache, resolveAvailability} = cachedData); } // resolve missing blobs @@ -100,12 +103,12 @@ export async function unavailableBeaconBlobsByRoot( // check and see if all blobs are now available and in that case resolve availability // if not this will error and the leftover blobs will be tried from another peer const allBlobs = getBlockInputBlobs(blobsCache); - const {blobs, blobsBytes} = allBlobs; + const {blobs} = allBlobs; if (blobs.length !== blobKzgCommitmentsLen) { throw Error(`Not all blobs fetched missingBlobs=${blobKzgCommitmentsLen - blobs.length}`); } - - resolveAvailability({...allBlobs, blobsSource: BlobsSource.byRoot}); + const blockData = {fork: cachedData.fork, ...allBlobs, blobsSource: BlobsSource.byRoot} as BlockInputDataBlobs; + resolveAvailability(blockData); metrics?.syncUnknownBlock.resolveAvailabilitySource.inc({source: BlockInputAvailabilitySource.UNKNOWN_SYNC}); - return getBlockInput.postDeneb(config, block, BlockSource.byRoot, blobs, BlobsSource.byRoot, blockBytes, blobsBytes); + return getBlockInput.availableData(config, block, BlockSource.byRoot, blockBytes, blockData); } diff --git a/packages/beacon-node/src/sync/interface.ts b/packages/beacon-node/src/sync/interface.ts index 4380353f7760..c1e83cb476fb 100644 --- a/packages/beacon-node/src/sync/interface.ts +++ b/packages/beacon-node/src/sync/interface.ts @@ -79,7 +79,7 @@ export type UnknownBlock = PendingBlockCommon & { parentBlockRootHex: null; } & ( | {unknownBlockType: PendingBlockType.UNKNOWN_BLOCK; blockInput: null} - | {unknownBlockType: PendingBlockType.UNKNOWN_BLOBS; blockInput: BlockInput & {type: BlockInputType.blobsPromise}} + | {unknownBlockType: PendingBlockType.UNKNOWN_BLOBS; blockInput: BlockInput & {type: BlockInputType.dataPromise}} | {unknownBlockType: PendingBlockType.UNKNOWN_BLOCKINPUT; blockInput: NullBlockInput} ); diff --git a/packages/beacon-node/src/sync/range/chain.ts b/packages/beacon-node/src/sync/range/chain.ts index ed67004bd128..41bbce3da820 100644 --- a/packages/beacon-node/src/sync/range/chain.ts +++ b/packages/beacon-node/src/sync/range/chain.ts @@ -402,9 +402,9 @@ export class SyncChain { batch.downloadingSuccess(res.result); let hasPostDenebBlocks = false; const blobs = res.result.reduce((acc, blockInput) => { - hasPostDenebBlocks ||= blockInput.type === BlockInputType.postDeneb; + hasPostDenebBlocks ||= blockInput.type === BlockInputType.availableData; return hasPostDenebBlocks - ? acc + (blockInput.type === BlockInputType.postDeneb ? blockInput.blobs.length : 0) + ? acc + (blockInput.type === BlockInputType.availableData ? blockInput.blockData.blobs.length : 0) : 0; }, 0); const downloadInfo = {blocks: res.result.length}; diff --git a/packages/beacon-node/src/sync/unknownBlock.ts b/packages/beacon-node/src/sync/unknownBlock.ts index c252f3087c82..3c15b32eb8d8 100644 --- a/packages/beacon-node/src/sync/unknownBlock.ts +++ b/packages/beacon-node/src/sync/unknownBlock.ts @@ -509,8 +509,8 @@ export class UnknownBlockSync { unavailableBlockInput: BlockInput | NullBlockInput, connectedPeers: PeerIdStr[] ): Promise<{blockInput: BlockInput; peerIdStr: string}> { - if (unavailableBlockInput.block !== null && unavailableBlockInput.type !== BlockInputType.blobsPromise) { - return {blockInput: unavailableBlockInput as BlockInput, peerIdStr: ""}; + if (unavailableBlockInput.block !== null && unavailableBlockInput.type !== BlockInputType.dataPromise) { + return {blockInput: unavailableBlockInput, peerIdStr: ""}; } const shuffledPeers = shuffle(connectedPeers); @@ -529,7 +529,7 @@ export class UnknownBlockSync { .BeaconBlock.hashTreeRoot(unavailableBlock.message); blockRootHex = toHexString(blockRoot); blobKzgCommitmentsLen = (unavailableBlock.message.body as deneb.BeaconBlockBody).blobKzgCommitments.length; - pendingBlobs = blobKzgCommitmentsLen - unavailableBlockInput.blobsCache.size; + pendingBlobs = blobKzgCommitmentsLen - unavailableBlockInput.cachedData.blobsCache.size; } let lastError: Error | null = null; diff --git a/packages/beacon-node/src/sync/utils/pendingBlocksTree.ts b/packages/beacon-node/src/sync/utils/pendingBlocksTree.ts index 32290b382e2c..0feb15862408 100644 --- a/packages/beacon-node/src/sync/utils/pendingBlocksTree.ts +++ b/packages/beacon-node/src/sync/utils/pendingBlocksTree.ts @@ -65,7 +65,7 @@ export function getUnknownAndAncestorBlocks(blocks: Map): const parentHex = block.parentBlockRootHex; if ( block.status === PendingBlockStatus.pending && - (block.blockInput?.block == null || block.blockInput?.type === BlockInputType.blobsPromise) && + (block.blockInput?.block == null || block.blockInput?.type === BlockInputType.dataPromise) && parentHex == null ) { unknowns.push(block); diff --git a/packages/beacon-node/test/e2e/network/onWorker/dataSerialization.test.ts b/packages/beacon-node/test/e2e/network/onWorker/dataSerialization.test.ts index 874faea20d14..09c74f1f5dfe 100644 --- a/packages/beacon-node/test/e2e/network/onWorker/dataSerialization.test.ts +++ b/packages/beacon-node/test/e2e/network/onWorker/dataSerialization.test.ts @@ -15,7 +15,13 @@ import { ReqRespMethod, networkEventDirection, } from "../../../../src/network/index.js"; -import {BlockInputType, BlockSource, BlockInputBlobs, BlockInput} from "../../../../src/chain/blocks/types.js"; +import { + BlockInputType, + BlockSource, + BlockInput, + BlockInputDataBlobs, + CachedData, +} from "../../../../src/chain/blocks/types.js"; import {ZERO_HASH, ZERO_HASH_HEX} from "../../../../src/constants/constants.js"; import {IteratorEventType} from "../../../../src/util/asyncIterableToEvents.js"; import {NetworkWorkerApi} from "../../../../src/network/core/index.js"; @@ -81,7 +87,7 @@ describe.skip("data serialization through worker boundary", function () { }, [NetworkEvent.unknownBlockParent]: { blockInput: { - type: BlockInputType.preDeneb, + type: BlockInputType.preData, block: ssz.capella.SignedBeaconBlock.defaultValue(), source: BlockSource.gossip, blockBytes: ZERO_HASH, @@ -246,21 +252,21 @@ describe.skip("data serialization through worker boundary", function () { type Resolves> = T extends Promise ? (U extends void ? null : U) : never; function getEmptyBlockInput(): BlockInput { - let resolveAvailability: ((blobs: BlockInputBlobs) => void) | null = null; - const availabilityPromise = new Promise((resolveCB) => { + let resolveAvailability: ((blobs: BlockInputDataBlobs) => void) | null = null; + const availabilityPromise = new Promise((resolveCB) => { resolveAvailability = resolveCB; }); if (resolveAvailability === null) { throw Error("Promise Constructor was not executed immediately"); } const blobsCache = new Map(); + + const cachedData = {fork: ForkName.deneb, blobsCache, availabilityPromise, resolveAvailability} as CachedData; return { - type: BlockInputType.blobsPromise, + type: BlockInputType.dataPromise, block: ssz.deneb.SignedBeaconBlock.defaultValue(), source: BlockSource.gossip, blockBytes: ZERO_HASH, - blobsCache, - availabilityPromise, - resolveAvailability, + cachedData, }; } diff --git a/packages/beacon-node/test/e2e/sync/unknownBlockSync.test.ts b/packages/beacon-node/test/e2e/sync/unknownBlockSync.test.ts index a4e390533b29..31f640269db7 100644 --- a/packages/beacon-node/test/e2e/sync/unknownBlockSync.test.ts +++ b/packages/beacon-node/test/e2e/sync/unknownBlockSync.test.ts @@ -122,7 +122,7 @@ describe("sync / unknown block sync", function () { await connected; loggerNodeA.info("Node A connected to Node B"); - const headInput = getBlockInput.preDeneb(config, head, BlockSource.gossip, null); + const headInput = getBlockInput.preData(config, head, BlockSource.gossip, null); switch (event) { case NetworkEvent.unknownBlockParent: diff --git a/packages/beacon-node/test/mocks/mockedBeaconChain.ts b/packages/beacon-node/test/mocks/mockedBeaconChain.ts index 39b62b597076..b63eb11435ca 100644 --- a/packages/beacon-node/test/mocks/mockedBeaconChain.ts +++ b/packages/beacon-node/test/mocks/mockedBeaconChain.ts @@ -51,6 +51,7 @@ vi.mock("@lodestar/fork-choice", async (importActual) => { getHeadRoot: vi.fn(), getDependentRoot: vi.fn(), getBlockHex: vi.fn(), + getBlock: vi.fn(), getAllAncestorBlocks: vi.fn(), getAllNonAncestorBlocks: vi.fn(), iterateAncestorBlocks: vi.fn(), diff --git a/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts b/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts index 2e0c3500bea5..60ff6ce48302 100644 --- a/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts +++ b/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts @@ -8,7 +8,7 @@ import { newFilledArray, } from "@lodestar/state-transition"; import {HISTORICAL_ROOTS_LIMIT, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {ExecutionStatus, ForkChoice, IForkChoiceStore, ProtoArray} from "@lodestar/fork-choice"; +import {ExecutionStatus, ForkChoice, IForkChoiceStore, ProtoArray, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {ssz} from "@lodestar/types"; // eslint-disable-next-line import/no-relative-packages import {generatePerfTestCachedStateAltair} from "../../../../../state-transition/test/perf/util.js"; @@ -66,6 +66,7 @@ describe(`getAttestationsForBlock vc=${vc}`, () => { executionStatus: ExecutionStatus.PreMerge, timeliness: false, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, originalState.slot ); @@ -90,6 +91,7 @@ describe(`getAttestationsForBlock vc=${vc}`, () => { executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge, timeliness: false, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, slot ); diff --git a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts index 19d33072bd7b..a21c28adaec0 100644 --- a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts +++ b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts @@ -113,7 +113,7 @@ describe.skip("verify+import blocks - range sync perf test", () => { }, fn: async (chain) => { const blocksImport = blocks.value.map((block) => - getBlockInput.preDeneb(chain.config, block, BlockSource.byRange, null) + getBlockInput.preData(chain.config, block, BlockSource.byRange, null) ); await chain.processChainSegment(blocksImport, { diff --git a/packages/beacon-node/test/spec/presets/fork_choice.test.ts b/packages/beacon-node/test/spec/presets/fork_choice.test.ts index 3b5d83bbeb95..67cb35972d77 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -7,7 +7,7 @@ import {CheckpointWithHex, ForkChoice} from "@lodestar/fork-choice"; import {phase0, allForks, bellatrix, ssz, RootHex, deneb} from "@lodestar/types"; import {bnToNum, fromHex} from "@lodestar/utils"; import {createBeaconConfig} from "@lodestar/config"; -import {ACTIVE_PRESET, ForkSeq, isForkBlobs} from "@lodestar/params"; +import {ACTIVE_PRESET, ForkSeq, isForkBlobs, ForkName} from "@lodestar/params"; import {BeaconChain, ChainEvent} from "../../../src/chain/index.js"; import {ClockEvent} from "../../../src/util/clock.js"; import {computeInclusionProof} from "../../../src/util/blobs.js"; @@ -212,17 +212,14 @@ const forkChoiceTest = }; }); - blockImport = getBlockInput.postDeneb( - config, - signedBlock, - BlockSource.gossip, - blobSidecars, - BlobsSource.gossip, - null, - [null] - ); + blockImport = getBlockInput.availableData(config, signedBlock, BlockSource.gossip, null, { + fork: ForkName.deneb, + blobs: blobSidecars, + blobsBytes: [null], + blobsSource: BlobsSource.gossip, + }); } else { - blockImport = getBlockInput.preDeneb(config, signedBlock, BlockSource.gossip, null); + blockImport = getBlockInput.preData(config, signedBlock, BlockSource.gossip, null); } await chain.processBlock(blockImport, { 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 e1ca3a084647..0868834e619e 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 @@ -51,6 +51,7 @@ describe("api/validator - produceBlockV2", function () { modules.chain.recomputeForkChoiceHead.mockReturnValue( generateProtoBlock({blockRoot: toHexString(parentBlockRoot)}) ); + modules.chain.forkChoice.getBlock.mockReturnValue(generateProtoBlock({blockRoot: toHexString(parentBlockRoot)})); modules.chain.produceBlock.mockResolvedValue({ block: fullBlock, executionPayloadValue, 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 309d68a9c9ec..851bc9d0f33b 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 @@ -9,6 +9,7 @@ import {ApiTestModules, getApiTestModules} from "../../../../utils/api.js"; 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"; /* eslint-disable @typescript-eslint/naming-convention */ describe("api/validator - produceBlockV3", function () { @@ -88,6 +89,7 @@ describe("api/validator - produceBlockV3", function () { blockRoot: toHexString(fullBlock.parentRoot), } as ProtoBlock); modules.chain.getProposerHead.mockReturnValue({blockRoot: toHexString(fullBlock.parentRoot)} as ProtoBlock); + modules.chain.forkChoice.getBlock.mockReturnValue(zeroProtoBlock); if (enginePayloadValue !== null) { const commonBlockBody: CommonBlockBody = { diff --git a/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts b/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts index 71ab5688a602..e2af3ecd5cae 100644 --- a/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts +++ b/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts @@ -126,7 +126,7 @@ function verifyBlocksSanityChecks( ): {relevantBlocks: allForks.SignedBeaconBlock[]; parentSlots: Slot[]; parentBlock: ProtoBlock | null} { const {relevantBlocks, parentSlots, parentBlock} = verifyBlocksImportSanityChecks( modules, - blocks.map((block) => getBlockInput.preDeneb(config, block, BlockSource.byRange, null)), + blocks.map((block) => getBlockInput.preData(config, block, BlockSource.byRange, null)), opts ); return { diff --git a/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts b/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts index 20b6fc8d7a00..6b96a0d1172f 100644 --- a/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts +++ b/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; import {describe, it, expect, beforeEach, beforeAll, vi} from "vitest"; import {config} from "@lodestar/config/default"; -import {CheckpointWithHex, ExecutionStatus, ForkChoice} from "@lodestar/fork-choice"; +import {CheckpointWithHex, ExecutionStatus, ForkChoice, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {FAR_FUTURE_EPOCH, MAX_EFFECTIVE_BALANCE} from "@lodestar/params"; import { CachedBeaconStateAllForks, @@ -46,6 +46,7 @@ describe("LodestarForkChoice", function () { justifiedBalances[1] = 2; justifiedBalances[2] = 3; const executionStatus = ExecutionStatus.PreMerge; + const dataAvailabilityStatus = DataAvailabilityStatus.PreData; const blockDelaySec = 0; const hashBlock = (block: phase0.BeaconBlock): string => toHexString(ssz.phase0.BeaconBlock.hashTreeRoot(block)); @@ -93,15 +94,43 @@ describe("LodestarForkChoice", function () { const currentSlot = childBlock.message.slot; forkChoice.updateTime(currentSlot); - forkChoice.onBlock(targetBlock.message, targetState, blockDelaySec, currentSlot, executionStatus); - forkChoice.onBlock(orphanedBlock.message, orphanedState, blockDelaySec, currentSlot, executionStatus); + forkChoice.onBlock( + targetBlock.message, + targetState, + blockDelaySec, + currentSlot, + executionStatus, + dataAvailabilityStatus + ); + forkChoice.onBlock( + orphanedBlock.message, + orphanedState, + blockDelaySec, + currentSlot, + executionStatus, + dataAvailabilityStatus + ); let head = forkChoice.getHead(); expect(head.slot).toBe(orphanedBlock.message.slot); - forkChoice.onBlock(parentBlock.message, parentState, blockDelaySec, currentSlot, executionStatus); + forkChoice.onBlock( + parentBlock.message, + parentState, + blockDelaySec, + currentSlot, + executionStatus, + dataAvailabilityStatus + ); // tie break condition causes head to be orphaned block (based on hex root comparison) head = forkChoice.getHead(); expect(head.slot).toBe(orphanedBlock.message.slot); - forkChoice.onBlock(childBlock.message, childState, blockDelaySec, currentSlot, executionStatus); + forkChoice.onBlock( + childBlock.message, + childState, + blockDelaySec, + currentSlot, + executionStatus, + dataAvailabilityStatus + ); head = forkChoice.getHead(); // without vote, head gets stuck at orphaned block expect(head.slot).toBe(orphanedBlock.message.slot); @@ -160,19 +189,19 @@ describe("LodestarForkChoice", function () { const currentSlot = 128; forkChoice.updateTime(currentSlot); - forkChoice.onBlock(block08.message, state08, blockDelaySec, currentSlot, executionStatus); - forkChoice.onBlock(block12.message, state12, blockDelaySec, currentSlot, executionStatus); - forkChoice.onBlock(block16.message, state16, blockDelaySec, currentSlot, executionStatus); - forkChoice.onBlock(block20.message, state20, blockDelaySec, currentSlot, executionStatus); - forkChoice.onBlock(block24.message, state24, blockDelaySec, currentSlot, executionStatus); - forkChoice.onBlock(block28.message, state28, blockDelaySec, currentSlot, executionStatus); + forkChoice.onBlock(block08.message, state08, blockDelaySec, currentSlot, executionStatus, dataAvailabilityStatus); + forkChoice.onBlock(block12.message, state12, blockDelaySec, currentSlot, executionStatus, dataAvailabilityStatus); + forkChoice.onBlock(block16.message, state16, blockDelaySec, currentSlot, executionStatus, dataAvailabilityStatus); + forkChoice.onBlock(block20.message, state20, blockDelaySec, currentSlot, executionStatus, dataAvailabilityStatus); + forkChoice.onBlock(block24.message, state24, blockDelaySec, currentSlot, executionStatus, dataAvailabilityStatus); + forkChoice.onBlock(block28.message, state28, blockDelaySec, currentSlot, executionStatus, dataAvailabilityStatus); expect(forkChoice.getAllAncestorBlocks(hashBlock(block16.message))).toHaveLength(3); expect(forkChoice.getAllAncestorBlocks(hashBlock(block24.message))).toHaveLength(5); expect(forkChoice.getBlockHex(hashBlock(block08.message))).not.toBeNull(); expect(forkChoice.getBlockHex(hashBlock(block12.message))).not.toBeNull(); expect(forkChoice.hasBlockHex(hashBlock(block08.message))).toBe(true); expect(forkChoice.hasBlockHex(hashBlock(block12.message))).toBe(true); - forkChoice.onBlock(block32.message, state32, blockDelaySec, currentSlot, executionStatus); + forkChoice.onBlock(block32.message, state32, blockDelaySec, currentSlot, executionStatus, dataAvailabilityStatus); forkChoice.prune(hashBlock(block16.message)); expect(forkChoice.getAllAncestorBlocks(hashBlock(block16.message)).length).toBeWithMessage( 0, @@ -202,10 +231,38 @@ describe("LodestarForkChoice", function () { const currentSlot = 35; const {block: childBlock, state: childState} = makeChild({block: parentBlock, state: parentState}, currentSlot); forkChoice.updateTime(currentSlot); - forkChoice.onBlock(targetBlock.message, targetState, blockDelaySec, currentSlot, executionStatus); - forkChoice.onBlock(orphanedBlock.message, orphanedState, blockDelaySec, currentSlot, executionStatus); - forkChoice.onBlock(parentBlock.message, parentState, blockDelaySec, currentSlot, executionStatus); - forkChoice.onBlock(childBlock.message, childState, blockDelaySec, currentSlot, executionStatus); + forkChoice.onBlock( + targetBlock.message, + targetState, + blockDelaySec, + currentSlot, + executionStatus, + dataAvailabilityStatus + ); + forkChoice.onBlock( + orphanedBlock.message, + orphanedState, + blockDelaySec, + currentSlot, + executionStatus, + dataAvailabilityStatus + ); + forkChoice.onBlock( + parentBlock.message, + parentState, + blockDelaySec, + currentSlot, + executionStatus, + dataAvailabilityStatus + ); + forkChoice.onBlock( + childBlock.message, + childState, + blockDelaySec, + currentSlot, + executionStatus, + dataAvailabilityStatus + ); const childBlockRoot = toHexString(ssz.phase0.BeaconBlock.hashTreeRoot(childBlock.message)); // the old way to get non canonical blocks const nonCanonicalSummaries = forkChoice @@ -244,17 +301,38 @@ describe("LodestarForkChoice", function () { blockW.message.parentRoot = finalizedRoot; const stateW = runStateTransition(anchorState, blockW); blockW.message.stateRoot = stateW.hashTreeRoot(); - forkChoice.onBlock(blockW.message, stateW, blockDelaySec, blockW.message.slot, executionStatus); + forkChoice.onBlock( + blockW.message, + stateW, + blockDelaySec, + blockW.message.slot, + executionStatus, + dataAvailabilityStatus + ); // X const {block: blockX, state: stateX} = makeChild({block: blockW, state: stateW}, 12); stateX.blockRoots.set(blockW.message.slot, ssz.phase0.BeaconBlock.hashTreeRoot(blockW.message)); - forkChoice.onBlock(blockX.message, stateX, blockDelaySec, blockX.message.slot, executionStatus); + forkChoice.onBlock( + blockX.message, + stateX, + blockDelaySec, + blockX.message.slot, + executionStatus, + dataAvailabilityStatus + ); // Y, same epoch to X const {block: blockY, state: stateY} = makeChild({block: blockX, state: stateX}, 13); stateY.blockRoots.set(blockW.message.slot, ssz.phase0.BeaconBlock.hashTreeRoot(blockW.message)); - forkChoice.onBlock(blockY.message, stateY, blockDelaySec, blockY.message.slot, executionStatus); + forkChoice.onBlock( + blockY.message, + stateY, + blockDelaySec, + blockY.message.slot, + executionStatus, + dataAvailabilityStatus + ); // Y and Z are candidates for new head, make more attestations on Y forkChoice.updateTime(blockY.message.slot); @@ -288,7 +366,14 @@ describe("LodestarForkChoice", function () { epoch: computeEpochAtSlot(blockW.message.slot), }); forkChoice.updateTime(blockZ.message.slot); - forkChoice.onBlock(blockZ.message, stateZ, blockDelaySec, blockZ.message.slot, executionStatus); + forkChoice.onBlock( + blockZ.message, + stateZ, + blockDelaySec, + blockZ.message.slot, + executionStatus, + dataAvailabilityStatus + ); const head = forkChoice.updateHead(); expect(head.blockRoot).toBeWithMessage( diff --git a/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts b/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts index e91743ef3aee..2a3275536f22 100644 --- a/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts +++ b/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts @@ -165,9 +165,9 @@ function parseResponseType(expectedRes: string | null): BlockInputType | null | case null: return null; case "pd": - return BlockInputType.postDeneb; + return BlockInputType.availableData; case "bp": - return BlockInputType.blobsPromise; + return BlockInputType.dataPromise; default: return Error(expectedRes); } diff --git a/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts b/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts index 1fdedc1dda28..3fb9cb8e1c79 100644 --- a/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts +++ b/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts @@ -1,9 +1,10 @@ import {describe, it, expect, beforeAll} from "vitest"; import {ssz, deneb} from "@lodestar/types"; +import {ForkName} from "@lodestar/params"; import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {beaconBlocksMaybeBlobsByRange} from "../../../src/network/reqresp/index.js"; -import {BlockInputType, BlockSource, BlobsSource} from "../../../src/chain/blocks/types.js"; +import {BlockSource, BlobsSource, getBlockInput} from "../../../src/chain/blocks/types.js"; import {initCKZG, loadEthereumTrustedSetup} from "../../../src/util/kzg.js"; import {INetwork} from "../../../src/network/interface.js"; import {ZERO_HASH} from "../../../src/constants/constants.js"; @@ -99,15 +100,12 @@ describe("beaconBlocksMaybeBlobsByRange", () => { const expectedResponse = blocksWithBlobs.map(([block, blobSidecars]) => { const blobs = blobSidecars !== undefined ? blobSidecars : []; - return { - type: BlockInputType.postDeneb, - block, - source: BlockSource.byRange, + return getBlockInput.availableData(config, block, BlockSource.byRange, null, { + fork: ForkName.deneb, blobs, blobsSource: BlobsSource.byRange, - blockBytes: null, blobsBytes: blobs.map(() => null), - }; + }); }); const network = { diff --git a/packages/beacon-node/test/unit/sync/range/batch.test.ts b/packages/beacon-node/test/unit/sync/range/batch.test.ts index caf8d5ea82e4..02f49226c11f 100644 --- a/packages/beacon-node/test/unit/sync/range/batch.test.ts +++ b/packages/beacon-node/test/unit/sync/range/batch.test.ts @@ -12,7 +12,7 @@ describe("sync / range / batch", () => { const startEpoch = 0; const peer = validPeerIdStr; const blocksDownloaded = [ - getBlockInput.preDeneb(config, ssz.phase0.SignedBeaconBlock.defaultValue(), BlockSource.byRange, null), + getBlockInput.preData(config, ssz.phase0.SignedBeaconBlock.defaultValue(), BlockSource.byRange, null), ]; it("Should return correct blockByRangeRequest", () => { diff --git a/packages/beacon-node/test/unit/sync/range/chain.test.ts b/packages/beacon-node/test/unit/sync/range/chain.test.ts index 5af96f0f1729..44d5cc7c173a 100644 --- a/packages/beacon-node/test/unit/sync/range/chain.test.ts +++ b/packages/beacon-node/test/unit/sync/range/chain.test.ts @@ -83,7 +83,7 @@ describe("sync / range / chain", () => { const shouldReject = badBlocks?.has(i); if (shouldReject) badBlocks?.delete(i); blocks.push( - getBlockInput.preDeneb( + getBlockInput.preData( config, { message: generateEmptyBlock(i), @@ -128,7 +128,7 @@ describe("sync / range / chain", () => { const blocks: BlockInput[] = []; for (let i = request.startSlot; i < request.startSlot + request.count; i += request.step) { blocks.push( - getBlockInput.preDeneb( + getBlockInput.preData( config, { message: generateEmptyBlock(i), diff --git a/packages/beacon-node/test/unit/sync/unknownBlock.test.ts b/packages/beacon-node/test/unit/sync/unknownBlock.test.ts index a0a8be65dd28..f6791fc29e9c 100644 --- a/packages/beacon-node/test/unit/sync/unknownBlock.test.ts +++ b/packages/beacon-node/test/unit/sync/unknownBlock.test.ts @@ -188,7 +188,7 @@ describe("sync by UnknownBlockSync", () => { syncService.subscribeToNetwork(); if (event === NetworkEvent.unknownBlockParent) { network.events?.emit(NetworkEvent.unknownBlockParent, { - blockInput: getBlockInput.preDeneb(config, blockC, BlockSource.gossip, null), + blockInput: getBlockInput.preData(config, blockC, BlockSource.gossip, null), peer, }); } else { diff --git a/packages/beacon-node/test/utils/state.ts b/packages/beacon-node/test/utils/state.ts index c9dc79d34ed0..af9ebd479340 100644 --- a/packages/beacon-node/test/utils/state.ts +++ b/packages/beacon-node/test/utils/state.ts @@ -12,7 +12,7 @@ import {allForks, altair, bellatrix, ssz} from "@lodestar/types"; import {createBeaconConfig, ChainForkConfig} from "@lodestar/config"; import {FAR_FUTURE_EPOCH, ForkName, ForkSeq, MAX_EFFECTIVE_BALANCE, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; -import {ExecutionStatus, ProtoBlock} from "@lodestar/fork-choice"; +import {ExecutionStatus, ProtoBlock, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {ZERO_HASH_HEX} from "../../src/constants/constants.js"; import {generateValidator, generateValidators} from "./validator.js"; import {getConfig} from "./config.js"; @@ -156,4 +156,5 @@ export const zeroProtoBlock: ProtoBlock = { timeliness: false, ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; diff --git a/packages/beacon-node/test/utils/typeGenerator.ts b/packages/beacon-node/test/utils/typeGenerator.ts index 90986a60eb4b..dde169ebce26 100644 --- a/packages/beacon-node/test/utils/typeGenerator.ts +++ b/packages/beacon-node/test/utils/typeGenerator.ts @@ -1,6 +1,6 @@ import {Slot} from "@lodestar/types"; import {phase0} from "@lodestar/types"; -import {ProtoBlock, ExecutionStatus} from "@lodestar/fork-choice"; +import {ProtoBlock, ExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {ssz} from "@lodestar/types"; import {fromHex} from "@lodestar/utils"; import {ZERO_HASH_HEX} from "../../src/constants/index.js"; @@ -43,7 +43,7 @@ export function generateProtoBlock(overrides: Partial = {}): ProtoBl timeliness: false, ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, - + dataAvailabilityStatus: DataAvailabilityStatus.PreData, ...overrides, } as ProtoBlock; } diff --git a/packages/beacon-node/test/utils/validationData/attestation.ts b/packages/beacon-node/test/utils/validationData/attestation.ts index 24e599852d86..c33d942dabc5 100644 --- a/packages/beacon-node/test/utils/validationData/attestation.ts +++ b/packages/beacon-node/test/utils/validationData/attestation.ts @@ -5,7 +5,7 @@ import { computeStartSlotAtEpoch, getShufflingDecisionBlock, } from "@lodestar/state-transition"; -import {ProtoBlock, IForkChoice, ExecutionStatus} from "@lodestar/fork-choice"; +import {ProtoBlock, IForkChoice, ExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {DOMAIN_BEACON_ATTESTER} from "@lodestar/params"; import {phase0, Slot, ssz} from "@lodestar/types"; import { @@ -78,6 +78,7 @@ export function getAttestationValidData(opts: AttestationValidDataOpts): { timeliness: false, ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; const shufflingCache = new ShufflingCache(); diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index b2e1a5314012..2433d68224f2 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -26,6 +26,7 @@ import { MaybeValidExecutionStatus, LVHExecResponse, ProtoNode, + DataAvailabilityStatus, } from "../protoArray/interface.js"; import {ProtoArray} from "../protoArray/protoArray.js"; import {ProtoArrayError, ProtoArrayErrorCode} from "../protoArray/errors.js"; @@ -467,7 +468,8 @@ export class ForkChoice implements IForkChoice { state: CachedBeaconStateAllForks, blockDelaySec: number, currentSlot: Slot, - executionStatus: MaybeValidExecutionStatus + executionStatus: MaybeValidExecutionStatus, + dataAvailabilityStatus: DataAvailabilityStatus ): ProtoBlock { const {parentRoot, slot} = block; const parentRootHex = toHexString(parentRoot); @@ -644,8 +646,13 @@ export class ForkChoice implements IForkChoice { executionPayloadBlockHash: toHexString(block.body.executionPayload.blockHash), executionPayloadNumber: block.body.executionPayload.blockNumber, executionStatus: this.getPostMergeExecStatus(executionStatus), + dataAvailabilityStatus, } - : {executionPayloadBlockHash: null, executionStatus: this.getPreMergeExecStatus(executionStatus)}), + : { + executionPayloadBlockHash: null, + executionStatus: this.getPreMergeExecStatus(executionStatus), + dataAvailabilityStatus: this.getPreMergeDataStatus(dataAvailabilityStatus), + }), }; this.protoArray.onBlock(protoBlock, currentSlot); @@ -1092,6 +1099,14 @@ export class ForkChoice implements IForkChoice { return executionStatus; } + private getPreMergeDataStatus(dataAvailabilityStatus: DataAvailabilityStatus): DataAvailabilityStatus.PreData { + if (dataAvailabilityStatus !== DataAvailabilityStatus.PreData) + throw Error( + `Invalid pre-merge data status: expected: ${DataAvailabilityStatus.PreData}, got ${dataAvailabilityStatus}` + ); + return dataAvailabilityStatus; + } + private getPostMergeExecStatus( executionStatus: MaybeValidExecutionStatus ): ExecutionStatus.Valid | ExecutionStatus.Syncing { diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index d91a338bbca5..aa5b86f0e64e 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -1,7 +1,13 @@ import {EffectiveBalanceIncrements} from "@lodestar/state-transition"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, Slot, ValidatorIndex, phase0, allForks, Root, RootHex} from "@lodestar/types"; -import {ProtoBlock, MaybeValidExecutionStatus, LVHExecResponse, ProtoNode} from "../protoArray/interface.js"; +import { + ProtoBlock, + MaybeValidExecutionStatus, + LVHExecResponse, + ProtoNode, + DataAvailabilityStatus, +} from "../protoArray/interface.js"; import {CheckpointWithHex} from "./store.js"; import {UpdateAndGetHeadOpt} from "./forkChoice.js"; @@ -129,7 +135,8 @@ export interface IForkChoice { state: CachedBeaconStateAllForks, blockDelaySec: number, currentSlot: Slot, - executionStatus: MaybeValidExecutionStatus + executionStatus: MaybeValidExecutionStatus, + dataAvailabilityStatus: DataAvailabilityStatus ): ProtoBlock; /** * Register `attestation` with the fork choice DAG so that it may influence future calls to `getHead`. diff --git a/packages/fork-choice/src/index.ts b/packages/fork-choice/src/index.ts index 12b678d7db2b..5974275fabc4 100644 --- a/packages/fork-choice/src/index.ts +++ b/packages/fork-choice/src/index.ts @@ -3,10 +3,11 @@ export type { ProtoBlock, ProtoNode, MaybeValidExecutionStatus, - BlockExecution, + BlockExtraMeta, LVHValidResponse, LVHInvalidResponse, } from "./protoArray/interface.js"; +export {DataAvailabilityStatus} from "./protoArray/interface.js"; export {ExecutionStatus} from "./protoArray/interface.js"; export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt, assertValidTerminalPowBlock} from "./forkChoice/forkChoice.js"; diff --git a/packages/fork-choice/src/protoArray/interface.ts b/packages/fork-choice/src/protoArray/interface.ts index e59c366cbf59..d60305a6ff72 100644 --- a/packages/fork-choice/src/protoArray/interface.ts +++ b/packages/fork-choice/src/protoArray/interface.ts @@ -35,20 +35,33 @@ export type LVHExecResponse = LVHValidResponse | LVHInvalidResponse; export type MaybeValidExecutionStatus = Exclude; -export type BlockExecution = +export enum DataAvailabilityStatus { + PreData = "PreData", + /* validator activities can't be performed on out of range data */ + OutOfRange = "OutOfRange", + Available = "Available", +} + +export type BlockExtraMeta = | { executionPayloadBlockHash: RootHex; executionPayloadNumber: UintNum64; executionStatus: Exclude; + dataAvailabilityStatus: DataAvailabilityStatus; } - | {executionPayloadBlockHash: null; executionStatus: ExecutionStatus.PreMerge}; + | { + executionPayloadBlockHash: null; + executionStatus: ExecutionStatus.PreMerge; + dataAvailabilityStatus: DataAvailabilityStatus.PreData; + }; + /** * A block that is to be applied to the fork choice * * A simplified version of BeaconBlock */ -export type ProtoBlock = BlockExecution & { +export type ProtoBlock = BlockExtraMeta & { /** * The slot is not necessary for ProtoArray, * it just exists so external components can easily query the block slot. diff --git a/packages/fork-choice/test/perf/forkChoice/util.ts b/packages/fork-choice/test/perf/forkChoice/util.ts index eace72d4d800..dbd049e257c1 100644 --- a/packages/fork-choice/test/perf/forkChoice/util.ts +++ b/packages/fork-choice/test/perf/forkChoice/util.ts @@ -1,6 +1,13 @@ import {fromHexString} from "@chainsafe/ssz"; import {config} from "@lodestar/config/default"; -import {ExecutionStatus, ForkChoice, IForkChoiceStore, ProtoBlock, ProtoArray} from "../../../src/index.js"; +import { + ExecutionStatus, + ForkChoice, + IForkChoiceStore, + ProtoBlock, + ProtoArray, + DataAvailabilityStatus, +} from "../../../src/index.js"; import {computeTotalBalance} from "../../../src/forkChoice/store.js"; const genesisSlot = 0; @@ -29,6 +36,7 @@ export function initializeForkChoice(opts: Opts): ForkChoice { executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, } as Omit, genesisSlot ); @@ -77,6 +85,7 @@ export function initializeForkChoice(opts: Opts): ForkChoice { executionStatus: ExecutionStatus.PreMerge, timeliness: false, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; protoArr.onBlock(block, block.slot); diff --git a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts index fb72a6705c37..fcb7376cffa8 100644 --- a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts @@ -12,6 +12,7 @@ import { ProtoArray, ExecutionStatus, EpochDifference, + DataAvailabilityStatus, } from "../../../src/index.js"; const rootStateBytePrefix = 0xaa; @@ -41,6 +42,7 @@ describe("Forkchoice", function () { executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, } as Omit, genesisSlot ); @@ -103,6 +105,7 @@ describe("Forkchoice", function () { executionStatus: ExecutionStatus.PreMerge, timeliness: false, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; }; diff --git a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts index 25d539a5e33b..cc14b5b57b92 100644 --- a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts @@ -4,7 +4,14 @@ import {config} from "@lodestar/config/default"; import {Slot} from "@lodestar/types"; import {toHex} from "@lodestar/utils"; import {INTERVALS_PER_SLOT, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {ForkChoice, IForkChoiceStore, ProtoArray, ExecutionStatus, ProtoBlock} from "../../../src/index.js"; +import { + ForkChoice, + IForkChoiceStore, + ProtoArray, + ExecutionStatus, + ProtoBlock, + DataAvailabilityStatus, +} from "../../../src/index.js"; import {NotReorgedReason} from "../../../src/forkChoice/interface.js"; import {getBlockRoot, getStateRoot} from "./forkChoice.test.js"; @@ -39,6 +46,7 @@ describe("Forkchoice / GetProposerHead", function () { executionStatus: ExecutionStatus.PreMerge, timeliness: false, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; const baseHeadBlock: ProtoBlockWithWeight = { @@ -63,6 +71,7 @@ describe("Forkchoice / GetProposerHead", function () { timeliness: false, weight: 29, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; const baseParentHeadBlock: ProtoBlockWithWeight = { @@ -86,6 +95,7 @@ describe("Forkchoice / GetProposerHead", function () { timeliness: false, weight: 212, // 240 - 29 + 1 + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; const fcStore: IForkChoiceStore = { diff --git a/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts b/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts index 9d007f1c27e9..cefb2b07df02 100644 --- a/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts +++ b/packages/fork-choice/test/unit/protoArray/executionStatusUpdates.test.ts @@ -4,9 +4,10 @@ import { ProtoArray, ExecutionStatus, MaybeValidExecutionStatus, - BlockExecution, + BlockExtraMeta, } from "../../../src/index.js"; import {LVHExecErrorCode} from "../../../src/protoArray/errors.js"; +import {DataAvailabilityStatus} from "../../../src/protoArray/interface.js"; type ValidationTestCase = { root: string; @@ -81,13 +82,18 @@ function setupForkChoice(): ProtoArray { for (const block of blocks) { const executionData = ( block.executionStatus === ExecutionStatus.PreMerge - ? {executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge} + ? { + executionPayloadBlockHash: null, + executionStatus: ExecutionStatus.PreMerge, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, + } : { executionPayloadBlockHash: block.root, executionPayloadNumber: block.slot, executionStatus: block.executionStatus, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, } - ) as BlockExecution; + ) as BlockExtraMeta; fc.onBlock( { slot: block.slot, diff --git a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts index e014615fd312..fdf1a1f3bae4 100644 --- a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts +++ b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts @@ -1,5 +1,5 @@ import {describe, it, expect} from "vitest"; -import {ProtoArray, ExecutionStatus} from "../../../src/index.js"; +import {ProtoArray, ExecutionStatus, DataAvailabilityStatus} from "../../../src/index.js"; describe("getCommonAncestor", () => { const blocks: {slot: number; root: string; parent: string}[] = [ @@ -43,6 +43,7 @@ describe("getCommonAncestor", () => { timeliness: false, ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, 0 ); @@ -68,6 +69,7 @@ describe("getCommonAncestor", () => { timeliness: false, ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, block.slot ); diff --git a/packages/fork-choice/test/unit/protoArray/protoArray.test.ts b/packages/fork-choice/test/unit/protoArray/protoArray.test.ts index fdebd4bba645..92dfc61d61b2 100644 --- a/packages/fork-choice/test/unit/protoArray/protoArray.test.ts +++ b/packages/fork-choice/test/unit/protoArray/protoArray.test.ts @@ -1,7 +1,7 @@ import {describe, it, expect} from "vitest"; import {RootHex} from "@lodestar/types"; -import {ProtoArray, ExecutionStatus} from "../../../src/index.js"; +import {ProtoArray, ExecutionStatus, DataAvailabilityStatus} from "../../../src/index.js"; describe("ProtoArray", () => { it("finalized descendant", () => { @@ -33,6 +33,7 @@ describe("ProtoArray", () => { timeliness: false, ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, genesisSlot ); @@ -58,6 +59,7 @@ describe("ProtoArray", () => { timeliness: false, ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, genesisSlot + 1 ); @@ -83,6 +85,7 @@ describe("ProtoArray", () => { timeliness: false, ...{executionPayloadBlockHash: null, executionStatus: ExecutionStatus.PreMerge}, + dataAvailabilityStatus: DataAvailabilityStatus.PreData, }, genesisSlot + 1 ); From f29a6db05624fc454e586790e4f977ec0817ed25 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 14 Jun 2024 21:31:59 +0200 Subject: [PATCH 22/55] test: fix failing browsers bundle tests (#6886) * Fix browser failing tests * Phrasing --------- Co-authored-by: Nico Flaig --- .../light-client/test/unit/webEsmBundle.browser.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/light-client/test/unit/webEsmBundle.browser.test.ts b/packages/light-client/test/unit/webEsmBundle.browser.test.ts index 64f944888184..defc421d7071 100644 --- a/packages/light-client/test/unit/webEsmBundle.browser.test.ts +++ b/packages/light-client/test/unit/webEsmBundle.browser.test.ts @@ -1,10 +1,16 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call */ -import {expect, describe, it, vi} from "vitest"; +import {expect, describe, it, vi, beforeAll} from "vitest"; +import {sleep} from "@lodestar/utils"; import {Lightclient, LightclientEvent, utils, transport} from "../../dist/lightclient.min.mjs"; describe("web bundle for lightclient", () => { vi.setConfig({testTimeout: 20_000}); + // Sometimes bundle takes some time to load in the browser + beforeAll(async () => { + await sleep(2000); + }); + it("should have a global interface", () => { expect((window as any)["lodestar"]["lightclient"]).toBeDefined(); }); From 7e35c924e7cf3ccc119f80f81f6de40c70ff4d29 Mon Sep 17 00:00:00 2001 From: Divyesh L <99556535+divyesh87@users.noreply.github.com> Date: Mon, 17 Jun 2024 20:30:40 +0530 Subject: [PATCH 23/55] test: update clock tests to run deterministically (#6828) * Fix test case * Lint --------- Co-authored-by: Nico Flaig --- packages/validator/test/unit/utils/clock.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/validator/test/unit/utils/clock.test.ts b/packages/validator/test/unit/utils/clock.test.ts index dfa9d386e663..d668d753014a 100644 --- a/packages/validator/test/unit/utils/clock.test.ts +++ b/packages/validator/test/unit/utils/clock.test.ts @@ -84,6 +84,13 @@ describe("util / Clock", function () { // eslint-disable-next-line @typescript-eslint/naming-convention const testConfig = {SECONDS_PER_SLOT: 12} as BeaconConfig; const genesisTime = Math.floor(new Date("2021-01-01").getTime() / 1000); + + // Tests can fail under certain time slots, overriding the system time + // with a specific value allows us to run tests deterministically + beforeEach(() => { + vi.setSystemTime(genesisTime * 1000); + }); + const testCase: {name: string; delta: number}[] = [ {name: "should return next slot after 11.5s", delta: 11.5}, {name: "should return next slot after 12s", delta: 12}, From 89ab742ab3597b588fa25876b47c37efbf30d0e9 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 18 Jun 2024 14:37:28 +0100 Subject: [PATCH 24/55] fix: avoid uncaught exception when polling validator indices (#6891) --- packages/validator/src/services/indices.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/validator/src/services/indices.ts b/packages/validator/src/services/indices.ts index dcc129cc591e..c6ef40b473e5 100644 --- a/packages/validator/src/services/indices.ts +++ b/packages/validator/src/services/indices.ts @@ -83,10 +83,8 @@ export class IndicesService { return this.pollValidatorIndicesPromise; } - this.pollValidatorIndicesPromise = this.pollValidatorIndicesInternal(pubkeysHex); - // Once the pollValidatorIndicesInternal() resolves or rejects null the cached promise so it can be called again. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.pollValidatorIndicesPromise.finally(() => { + this.pollValidatorIndicesPromise = this.pollValidatorIndicesInternal(pubkeysHex).finally(() => { + // Once the pollValidatorIndicesInternal() resolves or rejects null the cached promise so it can be called again. this.pollValidatorIndicesPromise = null; }); return this.pollValidatorIndicesPromise; From f37e50fb13a3b94e6a9a60fcacd7f27d3f6b375a Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 18 Jun 2024 15:58:28 +0200 Subject: [PATCH 25/55] fix: update holesky / sepolia repository layout (#6827) * fix: update holesky repository layout * fix sepolia url too * update sepolia repository * fix lint --- packages/cli/src/networks/holesky.ts | 5 ++--- packages/cli/src/networks/sepolia.ts | 4 ++-- packages/config/src/chainConfig/networks/holesky.ts | 2 +- packages/config/src/chainConfig/networks/sepolia.ts | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/networks/holesky.ts b/packages/cli/src/networks/holesky.ts index 0f89cadaed33..b86f6b543582 100644 --- a/packages/cli/src/networks/holesky.ts +++ b/packages/cli/src/networks/holesky.ts @@ -1,10 +1,9 @@ export {holeskyChainConfig as chainConfig} from "@lodestar/config/networks"; export const depositContractDeployBlock = 0; -export const genesisFileUrl = - "https://media.githubusercontent.com/media/eth-clients/holesky/main/custom_config_data/genesis.ssz"; +export const genesisFileUrl = "https://media.githubusercontent.com/media/eth-clients/holesky/main/metadata/genesis.ssz"; export const bootnodesFileUrl = - "https://raw.githubusercontent.com/eth-clients/holesky/main/custom_config_data/bootstrap_nodes.txt"; + "https://raw.githubusercontent.com/eth-clients/holesky/main/metadata/bootstrap_nodes.txt"; export const bootEnrs = [ "enr:-Ku4QFo-9q73SspYI8cac_4kTX7yF800VXqJW4Lj3HkIkb5CMqFLxciNHePmMt4XdJzHvhrCC5ADI4D_GkAsxGJRLnQBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAhnTT-AQFwAP__________gmlkgnY0gmlwhLKAiOmJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyk", diff --git a/packages/cli/src/networks/sepolia.ts b/packages/cli/src/networks/sepolia.ts index dc967968cf34..b0ac965071a8 100644 --- a/packages/cli/src/networks/sepolia.ts +++ b/packages/cli/src/networks/sepolia.ts @@ -1,9 +1,9 @@ export {sepoliaChainConfig as chainConfig} from "@lodestar/config/networks"; export const depositContractDeployBlock = 1273020; -export const genesisFileUrl = "https://raw.githubusercontent.com/eth-clients/merge-testnets/main/sepolia/genesis.ssz"; +export const genesisFileUrl = "https://github.com/eth-clients/sepolia/raw/main/metadata/genesis.ssz"; export const bootnodesFileUrl = - "https://raw.githubusercontent.com/eth-clients/merge-testnets/main/sepolia/bootstrap_nodes.txt"; + "https://raw.githubusercontent.com/eth-clients/sepolia/main/metadata/bootstrap_nodes.txt"; export const bootEnrs = [ "enr:-Iq4QMCTfIMXnow27baRUb35Q8iiFHSIDBJh6hQM5Axohhf4b6Kr_cOCu0htQ5WvVqKvFgY28893DHAg8gnBAXsAVqmGAX53x8JggmlkgnY0gmlwhLKAlv6Jc2VjcDI1NmsxoQK6S-Cii_KmfFdUJL2TANL3ksaKUnNXvTCv1tLwXs0QgIN1ZHCCIyk", diff --git a/packages/config/src/chainConfig/networks/holesky.ts b/packages/config/src/chainConfig/networks/holesky.ts index 3115caf1ab76..187543b871f2 100644 --- a/packages/config/src/chainConfig/networks/holesky.ts +++ b/packages/config/src/chainConfig/networks/holesky.ts @@ -4,7 +4,7 @@ import {ChainConfig} from "../types.js"; import {chainConfig as mainnet} from "../configs/mainnet.js"; // Holesky beacon chain config: -// https://github.com/eth-clients/holesky/blob/main/custom_config_data/config.yaml +// https://github.com/eth-clients/holesky/blob/main/metadata/config.yaml export const holeskyChainConfig: ChainConfig = { ...mainnet, diff --git a/packages/config/src/chainConfig/networks/sepolia.ts b/packages/config/src/chainConfig/networks/sepolia.ts index a3598299e294..ab6facbec72e 100644 --- a/packages/config/src/chainConfig/networks/sepolia.ts +++ b/packages/config/src/chainConfig/networks/sepolia.ts @@ -4,7 +4,7 @@ import {ChainConfig} from "../types.js"; import {chainConfig as mainnet} from "../configs/mainnet.js"; // Sepolia beacon chain config: -// https://github.com/eth-clients/sepolia/blob/main/bepolia/config.yaml +// https://github.com/eth-clients/sepolia/blob/main/metadata/config.yaml export const sepoliaChainConfig: ChainConfig = { ...mainnet, From 85dc0ba06d1adaf05df0e4aac8f74dee267c9625 Mon Sep 17 00:00:00 2001 From: twoeths Date: Wed, 19 Jun 2024 15:01:30 +0700 Subject: [PATCH 26/55] fix: prune invalid ssz objects (#6875) * fix: prune invalid ssz objects * fix: handle non-existing invalidSszObjects folder * Review PR * fix: expect empty dirs to be removed in unit tests --------- Co-authored-by: Nico Flaig --- packages/cli/src/cmds/beacon/handler.ts | 30 ++++++++++--------- packages/cli/src/util/pruneOldFilesInDir.ts | 12 ++++++-- .../test/unit/util/pruneOldFilesInDir.test.ts | 4 +-- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/cmds/beacon/handler.ts b/packages/cli/src/cmds/beacon/handler.ts index ec96081d3c75..e24089194185 100644 --- a/packages/cli/src/cmds/beacon/handler.ts +++ b/packages/cli/src/cmds/beacon/handler.ts @@ -101,20 +101,22 @@ export async function beaconHandler(args: BeaconArgs & GlobalArgs): Promise { - try { - pruneOldFilesInDir( - persistInvalidSszObjectsDir, - (args.persistInvalidSszObjectsRetentionHours ?? DEFAULT_RETENTION_SSZ_OBJECTS_HOURS) * HOURS_TO_MS - ); - } catch (e) { - logger.warn("Error pruning invalid SSZ objects", {persistInvalidSszObjectsDir}, e as Error); - } - // Run every ~1 hour - }, HOURS_TO_MS) - : null; + const {persistInvalidSszObjectsDir, persistInvalidSszObjects} = options.chain; + const pruneInvalidSSZObjectsInterval = + persistInvalidSszObjectsDir && persistInvalidSszObjects + ? setInterval(() => { + try { + const deletedFileCount = pruneOldFilesInDir( + persistInvalidSszObjectsDir, + (args.persistInvalidSszObjectsRetentionHours ?? DEFAULT_RETENTION_SSZ_OBJECTS_HOURS) * HOURS_TO_MS + ); + logger.info("Pruned invalid SSZ objects", {deletedFileCount}); + } catch (e) { + logger.warn("Error pruning invalid SSZ objects", {persistInvalidSszObjectsDir}, e as Error); + } + // Run every ~1 hour + }, HOURS_TO_MS) + : null; // Intercept SIGINT signal, to perform final ops before exiting onGracefulShutdown(async () => { diff --git a/packages/cli/src/util/pruneOldFilesInDir.ts b/packages/cli/src/util/pruneOldFilesInDir.ts index 28d3aa51d16b..3d5cc2b60bcb 100644 --- a/packages/cli/src/util/pruneOldFilesInDir.ts +++ b/packages/cli/src/util/pruneOldFilesInDir.ts @@ -1,17 +1,25 @@ import fs from "node:fs"; import path from "node:path"; -export function pruneOldFilesInDir(dirpath: string, maxAgeMs: number): void { +export function pruneOldFilesInDir(dirpath: string, maxAgeMs: number): number { + let deletedFileCount = 0; for (const entryName of fs.readdirSync(dirpath)) { const entryPath = path.join(dirpath, entryName); const stat = fs.statSync(entryPath); if (stat.isDirectory()) { - pruneOldFilesInDir(entryPath, maxAgeMs); + deletedFileCount += pruneOldFilesInDir(entryPath, maxAgeMs); } else if (stat.isFile()) { if (Date.now() - stat.mtimeMs > maxAgeMs) { fs.unlinkSync(entryPath); + deletedFileCount += 1; } } } + + // if all files are deleted, delete the directory + if (fs.readdirSync(dirpath).length === 0) { + fs.rmdirSync(dirpath); + } + return deletedFileCount; } diff --git a/packages/cli/test/unit/util/pruneOldFilesInDir.test.ts b/packages/cli/test/unit/util/pruneOldFilesInDir.test.ts index d88f684902e0..76285afff081 100644 --- a/packages/cli/test/unit/util/pruneOldFilesInDir.test.ts +++ b/packages/cli/test/unit/util/pruneOldFilesInDir.test.ts @@ -43,7 +43,7 @@ describe("pruneOldFilesInDir", () => { pruneOldFilesInDir(dataDir, DAYS_TO_MS); - expect(fs.readdirSync(nestedDir)).toHaveLength(0); + expect(fs.existsSync(nestedDir)).toBe(false); }); it("should handle empty directories", () => { @@ -52,7 +52,7 @@ describe("pruneOldFilesInDir", () => { pruneOldFilesInDir(emptyDir, DAYS_TO_MS); - expect(fs.readdirSync(emptyDir)).toHaveLength(0); + expect(fs.existsSync(emptyDir)).toBe(false); }); function createFileWithAge(path: string, ageInDays: number): void { From a1228762d4567f0daa33163b4d62a59344e57993 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 19 Jun 2024 17:38:41 +0100 Subject: [PATCH 27/55] fix: default to http status text if error response is empty (#6898) --- packages/api/src/utils/client/response.ts | 27 ++++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/api/src/utils/client/response.ts b/packages/api/src/utils/client/response.ts index b7039f4865ae..ed008273588a 100644 --- a/packages/api/src/utils/client/response.ts +++ b/packages/api/src/utils/client/response.ts @@ -161,11 +161,11 @@ export class ApiResponse extends Response { return null; } - return new ApiError(getErrorMessage(this.resolvedErrorBody()), this.status, this.definition.operationId); + return new ApiError(this.getErrorMessage(), this.status, this.definition.operationId); } async errorBody(): Promise { - if (!this._errorBody) { + if (this._errorBody === undefined) { this._errorBody = await this.text(); } return this._errorBody; @@ -179,23 +179,24 @@ export class ApiResponse extends Response { } private resolvedErrorBody(): string { - if (!this._errorBody) { + if (this._errorBody === undefined) { throw Error("errorBody() must be called first"); } return this._errorBody; } -} -function getErrorMessage(errBody: string): string { - try { - const errJson = JSON.parse(errBody) as {message?: string}; - if (errJson.message) { - return errJson.message; - } else { - return errBody; + private getErrorMessage(): string { + const errBody = this.resolvedErrorBody(); + try { + const errJson = JSON.parse(errBody) as {message?: string}; + if (errJson.message) { + return errJson.message; + } else { + return errBody; + } + } catch (e) { + return errBody || this.statusText; } - } catch (e) { - return errBody; } } From 0c5adec4eb9360a7f3f71e8d9c56020cdd95ce5d Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 20 Jun 2024 18:53:50 +0100 Subject: [PATCH 28/55] fix: move server api method invocation out of try-catch for parsing (#6890) * fix: move server api method invocation out of try-catch for parsing * Further simplify / group handler logic --- packages/api/src/utils/server/handler.ts | 85 ++++++++++++------------ 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/packages/api/src/utils/server/handler.ts b/packages/api/src/utils/server/handler.ts index fc2d1fe6ae6e..479212493885 100644 --- a/packages/api/src/utils/server/handler.ts +++ b/packages/api/src/utils/server/handler.ts @@ -11,10 +11,11 @@ import { SszRequestData, SszRequestMethods, isRequestWithoutBody, + RequestWithoutBodyCodec, } from "../types.js"; import {WireFormat, fromWireFormat, getWireFormat} from "../wireFormat.js"; import {ApiError} from "./error.js"; -import {ApplicationMethod, ApplicationResponse} from "./method.js"; +import {ApplicationMethod} from "./method.js"; export type FastifyHandler = fastify.RouteHandlerMethod< fastify.RawServerDefault, @@ -57,53 +58,44 @@ export function createFastifyHandler( } const responseWireFormat = responseMediaType !== null ? getWireFormat(responseMediaType) : null; - let response: ApplicationResponse; - try { - if (isRequestWithoutBody(definition)) { - response = await method(definition.req.parseReq(req as RequestData), { - sszBytes: null, - returnBytes: responseWireFormat === WireFormat.ssz, - }); + let requestWireFormat: WireFormat | null; + if (isRequestWithoutBody(definition)) { + requestWireFormat = null; + } else { + const contentType = req.headers[HttpHeader.ContentType]; + if (contentType === undefined && req.body === undefined) { + // Default to json parser if body is omitted. This is not possible for most + // routes as request will fail schema validation before this handler is called + requestWireFormat = WireFormat.json; } else { - let requestWireFormat: WireFormat; - const contentType = req.headers[HttpHeader.ContentType]; - if (contentType === undefined && req.body === undefined) { - // Default to json parser if body is omitted. This is not possible for most - // routes as request will fail schema validation before this handler is called - requestWireFormat = WireFormat.json; - } else { - if (contentType === undefined) { - throw new ApiError(400, "Content-Type header is required"); - } - const requestMediaType = parseContentTypeHeader(contentType); - if (requestMediaType === null) { - throw new ApiError(415, `Unsupported media type: ${contentType.split(";", 1)[0]}`); - } - requestWireFormat = getWireFormat(requestMediaType); + if (contentType === undefined) { + throw new ApiError(400, "Content-Type header is required"); } - - const {onlySupport} = definition.req as RequestWithBodyCodec; - if (onlySupport !== undefined && onlySupport !== requestWireFormat) { - throw new ApiError(415, `Endpoint only supports ${onlySupport.toUpperCase()} requests`); + const requestMediaType = parseContentTypeHeader(contentType); + if (requestMediaType === null) { + throw new ApiError(415, `Unsupported media type: ${contentType.split(";", 1)[0]}`); } + requestWireFormat = getWireFormat(requestMediaType); + } - switch (requestWireFormat) { - case WireFormat.json: - response = await method((definition.req as JsonRequestMethods).parseReqJson(req as JsonRequestData), { - sszBytes: null, - returnBytes: responseWireFormat === WireFormat.ssz, - }); - break; - case WireFormat.ssz: - response = await method( - (definition.req as SszRequestMethods).parseReqSsz(req as SszRequestData), - { - sszBytes: req.body as Uint8Array, - returnBytes: responseWireFormat === WireFormat.ssz, - } - ); - break; - } + const {onlySupport} = definition.req as RequestWithBodyCodec; + if (onlySupport !== undefined && onlySupport !== requestWireFormat) { + throw new ApiError(415, `Endpoint only supports ${onlySupport.toUpperCase()} requests`); + } + } + + let args: E["args"]; + try { + switch (requestWireFormat) { + case WireFormat.json: + args = (definition.req as JsonRequestMethods).parseReqJson(req as JsonRequestData); + break; + case WireFormat.ssz: + args = (definition.req as SszRequestMethods).parseReqSsz(req as SszRequestData); + break; + case null: + args = (definition.req as RequestWithoutBodyCodec).parseReq(req as RequestData); + break; } } catch (e) { if (e instanceof ApiError) throw e; @@ -111,6 +103,11 @@ export function createFastifyHandler( throw new ApiError(400, (e as Error).message); } + const response = await method(args, { + sszBytes: requestWireFormat === WireFormat.ssz ? (req.body as Uint8Array) : null, + returnBytes: responseWireFormat === WireFormat.ssz, + }); + if (response?.status !== undefined) { resp.statusCode = response.status; } From a074310f32d5864abfcc300a6c681554d6a016bc Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 20 Jun 2024 22:09:11 +0100 Subject: [PATCH 29/55] chore: add default message if server closes eventstream (#6902) --- packages/api/src/beacon/client/events.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/api/src/beacon/client/events.ts b/packages/api/src/beacon/client/events.ts index 97e2fdc40d75..35383083ee44 100644 --- a/packages/api/src/beacon/client/events.ts +++ b/packages/api/src/beacon/client/events.ts @@ -45,7 +45,8 @@ export function getClient(config: ChainForkConfig, baseUrl: string): ApiClient { // Ignore noisy errors due to beacon node being offline if (!errEs.message?.includes("ECONNREFUSED")) { - onError?.(new Error(errEs.message)); + // If there is no message it likely indicates that the server closed the connection + onError?.(new Error(errEs.message ?? "Server closed connection")); } // Consider 400 and 500 status errors unrecoverable, close the eventsource From a2c389fc97b03c415e3bccaef5cf2d512065b7a7 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 21 Jun 2024 15:58:21 +0100 Subject: [PATCH 30/55] chore: remove apis that are no longer part of beacon spec (#6901) * chore: remove apis that are no longer part of beacon spec * Update state ssz tests to deneb --- .../api/src/beacon/routes/beacon/block.ts | 26 +----- packages/api/src/beacon/routes/debug.ts | 92 +++---------------- packages/api/src/beacon/routes/validator.ts | 45 --------- .../beacon/genericServerTest/debug.test.ts | 34 +++---- .../api/test/unit/beacon/testData/beacon.ts | 4 - .../api/test/unit/beacon/testData/debug.ts | 8 -- .../test/unit/beacon/testData/validator.ts | 4 - .../src/api/impl/beacon/blocks/index.ts | 5 - .../beacon-node/src/api/impl/debug/index.ts | 16 ---- .../src/api/impl/validator/index.ts | 11 --- 10 files changed, 30 insertions(+), 215 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/block.ts b/packages/api/src/beacon/routes/beacon/block.ts index 380efcc9825a..76c2b3664a3f 100644 --- a/packages/api/src/beacon/routes/beacon/block.ts +++ b/packages/api/src/beacon/routes/beacon/block.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/naming-convention */ import {ContainerType, ListCompositeType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {allForks, Slot, ssz, RootHex, deneb, phase0, isSignedBlockContents} from "@lodestar/types"; +import {allForks, Slot, ssz, RootHex, deneb, isSignedBlockContents} from "@lodestar/types"; import {ForkName, ForkSeq} from "@lodestar/params"; import {Endpoint, RequestCodec, RouteDefinitions, Schema} from "../../../utils/index.js"; -import {EmptyMeta, EmptyMetaCodec, EmptyResponseCodec, EmptyResponseData, WithVersion} from "../../../utils/codecs.js"; +import {EmptyMeta, EmptyResponseCodec, EmptyResponseData, WithVersion} from "../../../utils/codecs.js"; import { ExecutionOptimisticAndFinalizedCodec, ExecutionOptimisticAndFinalizedMeta, @@ -66,19 +66,6 @@ export enum BroadcastValidation { } export type Endpoints = { - /** - * Get block - * Returns the complete `SignedBeaconBlock` for a given block ID. - */ - getBlock: Endpoint< - // ⏎ - "GET", - BlockArgs, - {params: {block_id: string}}, - phase0.SignedBeaconBlock, - EmptyMeta - >; - /** * Get block * Retrieves block details for given block id. @@ -220,15 +207,6 @@ const blockIdOnlyReq: RequestCodec { return { - getBlock: { - url: "/eth/v1/beacon/blocks/{block_id}", - method: "GET", - req: blockIdOnlyReq, - resp: { - data: ssz.phase0.SignedBeaconBlock, - meta: EmptyMetaCodec, - }, - }, getBlockV2: { url: "/eth/v2/beacon/blocks/{block_id}", method: "GET", diff --git a/packages/api/src/beacon/routes/debug.ts b/packages/api/src/beacon/routes/debug.ts index 2128f7204c6a..544cf795fdaf 100644 --- a/packages/api/src/beacon/routes/debug.ts +++ b/packages/api/src/beacon/routes/debug.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import {ContainerType, Type, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {allForks, ssz, StringType, phase0} from "@lodestar/types"; +import {allForks, ssz, StringType} from "@lodestar/types"; import { ArrayOf, EmptyArgs, @@ -14,8 +14,6 @@ import { import { ExecutionOptimisticFinalizedAndVersionCodec, ExecutionOptimisticFinalizedAndVersionMeta, - ExecutionOptimisticAndFinalizedCodec, - ExecutionOptimisticAndFinalizedMeta, } from "../../utils/metadata.js"; import {Endpoint, RouteDefinitions} from "../../utils/types.js"; import {WireFormat} from "../../utils/wireFormat.js"; @@ -25,7 +23,7 @@ import {StateArgs} from "./beacon/state.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes const stringType = new StringType(); -const ProtoNodeResponseType = new ContainerType( +const ProtoNodeType = new ContainerType( { executionPayloadBlockHash: stringType, executionPayloadNumber: ssz.UintNum64, @@ -51,14 +49,7 @@ const ProtoNodeResponseType = new ContainerType( }, {jsonCase: "eth2"} ); -const SlotRootType = new ContainerType( - { - slot: ssz.Slot, - root: stringType, - }, - {jsonCase: "eth2"} -); -const SlotRootExecutionOptimisticType = new ContainerType( +const DebugChainHeadType = new ContainerType( { slot: ssz.Slot, root: stringType, @@ -67,27 +58,13 @@ const SlotRootExecutionOptimisticType = new ContainerType( {jsonCase: "eth2"} ); -const ProtoNodeResponseListType = ArrayOf(ProtoNodeResponseType); -const SlotRootListType = ArrayOf(SlotRootType); -const SlotRootExecutionOptimisticListType = ArrayOf(SlotRootExecutionOptimisticType); +const ProtoNodeListType = ArrayOf(ProtoNodeType); +const DebugChainHeadListType = ArrayOf(DebugChainHeadType); -type ProtoNodeResponseList = ValueOf; -type SlotRootList = ValueOf; -type SlotRootExecutionOptimisticList = ValueOf; +type ProtoNodeList = ValueOf; +type DebugChainHeadList = ValueOf; export type Endpoints = { - /** - * Retrieves all possible chain heads (leaves of fork choice tree). - */ - getDebugChainHeads: Endpoint< - // ⏎ - "GET", - EmptyArgs, - EmptyRequest, - SlotRootList, - EmptyMeta - >; - /** * Retrieves all possible chain heads (leaves of fork choice tree). */ @@ -96,7 +73,7 @@ export type Endpoints = { "GET", EmptyArgs, EmptyRequest, - SlotRootExecutionOptimisticList, + DebugChainHeadList, EmptyMeta >; @@ -108,23 +85,10 @@ export type Endpoints = { "GET", EmptyArgs, EmptyRequest, - ProtoNodeResponseList, + ProtoNodeList, EmptyMeta >; - /** - * Get full BeaconState object - * Returns full BeaconState object for given stateId. - * Depending on `Accept` header it can be returned either as json or as bytes serialized by SSZ - */ - getState: Endpoint< - "GET", - StateArgs, - {params: {state_id: string}}, - phase0.BeaconState, - ExecutionOptimisticAndFinalizedMeta - >; - /** * Get full BeaconState object * Returns full BeaconState object for given stateId. @@ -139,27 +103,14 @@ export type Endpoints = { >; }; -// Default timeout is not sufficient to download state as JSON -const GET_STATE_TIMEOUT_MS = 5 * 60 * 1000; - export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { return { - getDebugChainHeads: { - url: "/eth/v1/debug/beacon/heads", - method: "GET", - req: EmptyRequestCodec, - resp: { - data: SlotRootListType, - meta: EmptyMetaCodec, - onlySupport: WireFormat.json, - }, - }, getDebugChainHeadsV2: { url: "/eth/v2/debug/beacon/heads", method: "GET", req: EmptyRequestCodec, resp: { - data: SlotRootExecutionOptimisticListType, + data: DebugChainHeadListType, meta: EmptyMetaCodec, onlySupport: WireFormat.json, }, @@ -169,29 +120,11 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId.toString()}}), - parseReq: ({params}) => ({stateId: params.state_id}), - schema: { - params: {state_id: Schema.StringRequired}, - }, - }, - resp: { - data: ssz.phase0.BeaconState, - meta: ExecutionOptimisticAndFinalizedCodec, - }, - init: { - timeoutMs: GET_STATE_TIMEOUT_MS, - }, - }, getStateV2: { url: "/eth/v2/debug/beacon/states/{state_id}", method: "GET", @@ -207,7 +140,8 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions; - /** - * Produce a new block, without signature. - * Requests a beacon node to produce a valid block, which can then be signed by a validator. - */ - produceBlock: Endpoint< - "GET", - { - /** The slot for which the block should be proposed. */ - slot: Slot; - /** The validator's randao reveal value */ - randaoReveal: BLSSignature; - /** Arbitrary data validator wants to include in block */ - graffiti: string; - }, - {params: {slot: number}; query: {randao_reveal: string; graffiti: string}}, - allForks.BeaconBlock, - VersionMeta - >; - /** * Requests a beacon node to produce a valid block, which can then be signed by a validator. * Metadata in the response indicates the type of block produced, and the supported types of block @@ -607,32 +588,6 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({ - params: {slot}, - query: {randao_reveal: toHexString(randaoReveal), graffiti: toGraffitiHex(graffiti)}, - }), - parseReq: ({params, query}) => ({ - slot: params.slot, - randaoReveal: fromHexString(query.randao_reveal), - graffiti: fromGraffitiHex(query.graffiti), - }), - schema: { - params: {slot: Schema.UintRequired}, - query: { - randao_reveal: Schema.StringRequired, - graffiti: Schema.String, - }, - }, - }, - resp: { - data: WithVersion((fork) => ssz[fork].BeaconBlock), - meta: VersionCodec, - }, - }, produceBlockV2: { url: "/eth/v2/validator/blocks/{slot}", method: "GET", diff --git a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts index 54158cda392c..f329382dbe46 100644 --- a/packages/api/test/unit/beacon/genericServerTest/debug.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/debug.test.ts @@ -23,7 +23,7 @@ describe("beacon / debug", () => { // Get state by SSZ - describe("getState() in SSZ format", () => { + describe("get state in SSZ format", () => { const mockApi = getMockApi(getDefinitions(config)); let baseUrl: string; let server: FastifyInstance; @@ -41,26 +41,22 @@ describe("beacon / debug", () => { if (server !== undefined) await server.close(); }); - for (const method of ["getState" as const, "getStateV2" as const]) { - it(method, async () => { - const state = ssz.phase0.BeaconState.defaultValue(); - const stateSerialized = ssz.phase0.BeaconState.serialize(state); - mockApi[method].mockResolvedValue({ - data: stateSerialized, - meta: {version: ForkName.phase0, executionOptimistic: false, finalized: false}, - }); - - const httpClient = new HttpClient({baseUrl}); - const client = getClient(config, httpClient); + it("getStateV2", async () => { + const state = ssz.deneb.BeaconState.defaultValue(); + const stateSerialized = ssz.deneb.BeaconState.serialize(state); + mockApi.getStateV2.mockResolvedValue({ + data: stateSerialized, + meta: {version: ForkName.deneb, executionOptimistic: false, finalized: false}, + }); - const res = await client[method]({stateId: "head"}, {responseWireFormat: WireFormat.ssz}); + const httpClient = new HttpClient({baseUrl}); + const client = getClient(config, httpClient); - expect(res.ok).toBe(true); + const res = await client.getStateV2({stateId: "head"}, {responseWireFormat: WireFormat.ssz}); - if (res.ok) { - expect(toHexString(res.ssz())).toBe(toHexString(stateSerialized)); - } - }); - } + expect(res.ok).toBe(true); + expect(res.wireFormat()).toBe(WireFormat.ssz); + expect(toHexString(res.ssz())).toBe(toHexString(stateSerialized)); + }); }); }); diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index 9dad5f0079a2..c7dde0077944 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -31,10 +31,6 @@ const validatorResponse: ValidatorResponse = { export const testData: GenericServerTestCases = { // block - getBlock: { - args: {blockId: "head"}, - res: {data: ssz.phase0.SignedBeaconBlock.defaultValue()}, - }, getBlockV2: { args: {blockId: "head"}, res: { diff --git a/packages/api/test/unit/beacon/testData/debug.ts b/packages/api/test/unit/beacon/testData/debug.ts index 386443c76f3d..aac3b379ff4d 100644 --- a/packages/api/test/unit/beacon/testData/debug.ts +++ b/packages/api/test/unit/beacon/testData/debug.ts @@ -7,10 +7,6 @@ import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; const rootHex = toHexString(Buffer.alloc(32, 1)); export const testData: GenericServerTestCases = { - getDebugChainHeads: { - args: undefined, - res: {data: [{slot: 1, root: rootHex}]}, - }, getDebugChainHeadsV2: { args: undefined, res: {data: [{slot: 1, root: rootHex, executionOptimistic: true}]}, @@ -45,10 +41,6 @@ export const testData: GenericServerTestCases = { ], }, }, - getState: { - args: {stateId: "head"}, - res: {data: ssz.phase0.BeaconState.defaultValue(), meta: {executionOptimistic: true, finalized: false}}, - }, getStateV2: { args: {stateId: "head"}, res: { diff --git a/packages/api/test/unit/beacon/testData/validator.ts b/packages/api/test/unit/beacon/testData/validator.ts index 3a92beb7ad27..11fd7dd26425 100644 --- a/packages/api/test/unit/beacon/testData/validator.ts +++ b/packages/api/test/unit/beacon/testData/validator.ts @@ -42,10 +42,6 @@ export const testData: GenericServerTestCases = { meta: {executionOptimistic: true}, }, }, - produceBlock: { - args: {slot: 32000, randaoReveal, graffiti}, - res: {data: ssz.phase0.BeaconBlock.defaultValue(), meta: {version: ForkName.phase0}}, - }, produceBlockV2: { args: { slot: 32000, 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 613ffeaacfdd..dc85daff8575 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -366,11 +366,6 @@ export function getBeaconBlockApi({ }; }, - async getBlock({blockId}) { - const {block} = await resolveBlockId(chain, blockId); - return {data: block}; - }, - async getBlockV2({blockId}) { const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); return { diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index c5c0eeda03a3..f1254ae1b7fb 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -1,6 +1,5 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; -import {phase0} from "@lodestar/types"; import {resolveStateId} from "../beacon/state/utils.js"; import {ApiModules} from "../types.js"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; @@ -10,13 +9,6 @@ export function getDebugApi({ config, }: Pick): ApplicationMethods { return { - async getDebugChainHeads() { - const heads = chain.forkChoice.getHeads(); - return { - data: heads.map((blockSummary) => ({slot: blockSummary.slot, root: blockSummary.blockRoot})), - }; - }, - async getDebugChainHeadsV2() { const heads = chain.forkChoice.getHeads(); return { @@ -41,14 +33,6 @@ export function getDebugApi({ return {data: nodes}; }, - async getState({stateId}, context) { - const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId, {allowRegen: true}); - return { - data: context?.returnBytes ? state.serialize() : (state.toValue() as phase0.BeaconState), - meta: {executionOptimistic, finalized}, - }; - }, - async getStateV2({stateId}, context) { const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId, {allowRegen: true}); return { diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 2c0958de5d5a..a74c9b2ec8cb 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -739,17 +739,6 @@ export function getValidatorApi({ } return { - async produceBlock({slot, randaoReveal, graffiti}) { - const {data, ...meta} = await produceEngineFullBlockOrContents(slot, randaoReveal, graffiti); - if (isForkBlobs(meta.version)) { - throw Error(`Invalid call to produceBlock for deneb+ fork=${meta.version}`); - } else { - // TODO: need to figure out why typescript requires typecasting here - // by typing of produceFullBlockOrContents respose it should have figured this out itself - return {data: data as allForks.BeaconBlock, meta}; - } - }, - async produceBlockV2({slot, randaoReveal, graffiti, ...opts}) { const {data, ...meta} = await produceEngineFullBlockOrContents(slot, randaoReveal, graffiti, opts); return {data, meta}; From 802a875a73148c01cb2e077cd24b08d4e6a8c4b4 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 24 Jun 2024 09:44:32 +0200 Subject: [PATCH 31/55] refactor: improve types package to use forks as generics (#6825) * Update the types to use generics * Update the types with default generics * Fix the block type * Add missing type * Add missing type * Add ssz types * Remove allForks namespace * Make ssz types generic * Update config package * Fix blok fork types * Fix api package types * Update types * Update state-transition package * Update config package * Fix types * Fix types for all packages * Update api package * Fix types for tests files * Fix ssz types * Fix build error * Fix lint errors * Fix lint errors * Fix type issues after rebase * Update redundant generics * Fix the type * Update types to use single generic * Fix all types * Fix the code as per feedback * Update the code with feedback * Update packages/types/README.md Co-authored-by: Nico Flaig * chore: review #6825 (#6887) Review #6825 * Fix publishBlockWrapper input type * Updated the type guard * Remove ssz instance type * Rename BlindedExecutionPayload to ExecutionPayloadheader * Update code as per feedback --------- Co-authored-by: Nico Flaig --- .../api/src/beacon/routes/beacon/block.ts | 44 +-- packages/api/src/beacon/routes/debug.ts | 6 +- packages/api/src/beacon/routes/events.ts | 21 +- packages/api/src/beacon/routes/lightclient.ts | 23 +- packages/api/src/beacon/routes/validator.ts | 14 +- packages/api/src/builder/routes.ts | 30 +- packages/api/src/utils/fork.ts | 31 +- .../api/test/unit/beacon/testData/beacon.ts | 4 +- .../src/api/impl/beacon/blocks/index.ts | 15 +- .../src/api/impl/beacon/blocks/utils.ts | 8 +- .../src/api/impl/validator/index.ts | 20 +- .../src/chain/blocks/importBlock.ts | 6 +- .../beacon-node/src/chain/blocks/index.ts | 4 +- .../beacon-node/src/chain/blocks/types.ts | 12 +- .../blocks/verifyBlocksExecutionPayloads.ts | 8 +- .../chain/blocks/verifyBlocksSignatures.ts | 8 +- packages/beacon-node/src/chain/chain.ts | 26 +- .../src/chain/errors/blockError.ts | 4 +- packages/beacon-node/src/chain/initState.ts | 9 +- packages/beacon-node/src/chain/interface.ts | 22 +- .../src/chain/lightClient/index.ts | 66 ++-- .../src/chain/lightClient/proofs.ts | 6 +- .../beacon-node/src/chain/opPools/opPool.ts | 9 +- .../chain/produceBlock/computeNewStateRoot.ts | 6 +- .../chain/produceBlock/produceBlockBody.ts | 32 +- .../validateBlobsAndKzgCommitments.ts | 4 +- .../beacon-node/src/chain/regen/interface.ts | 10 +- .../beacon-node/src/chain/regen/queued.ts | 6 +- packages/beacon-node/src/chain/regen/regen.ts | 4 +- .../src/chain/rewards/blockRewards.ts | 14 +- .../src/chain/rewards/syncCommitteeRewards.ts | 4 +- .../chain/seenCache/seenGossipBlockInput.ts | 6 +- .../beacon-node/src/chain/validation/block.ts | 4 +- .../validation/lightClientFinalityUpdate.ts | 4 +- .../validation/lightClientOptimisticUpdate.ts | 6 +- packages/beacon-node/src/db/buckets.ts | 2 +- .../beacon-node/src/db/repositories/block.ts | 10 +- .../src/db/repositories/blockArchive.ts | 26 +- .../src/db/repositories/blockArchiveIndex.ts | 9 +- .../db/repositories/lightclientBestUpdate.ts | 8 +- .../lightclientCheckpointHeader.ts | 8 +- .../src/db/single/preGenesisState.ts | 6 +- .../beacon-node/src/execution/builder/http.ts | 18 +- .../src/execution/builder/interface.ts | 16 +- .../beacon-node/src/execution/engine/http.ts | 6 +- .../src/execution/engine/interface.ts | 6 +- .../beacon-node/src/execution/engine/types.ts | 6 +- .../src/metrics/metrics/lodestar.ts | 4 +- .../src/metrics/validatorMonitor.ts | 6 +- .../src/network/gossip/interface.ts | 23 +- packages/beacon-node/src/network/interface.ts | 22 +- packages/beacon-node/src/network/network.ts | 38 ++- .../src/network/peers/peerManager.ts | 6 +- .../src/network/processor/gossipHandlers.ts | 4 +- .../src/network/reqresp/ReqRespBeaconNode.ts | 4 +- .../reqresp/beaconBlocksMaybeBlobsByRange.ts | 4 +- .../beacon-node/src/network/reqresp/types.ts | 10 +- .../utils/collectSequentialBlocksInRange.ts | 6 +- .../beacon-node/src/sync/backfill/backfill.ts | 4 +- .../beacon-node/src/sync/backfill/verify.ts | 12 +- packages/beacon-node/src/util/blobs.ts | 10 +- packages/beacon-node/src/util/multifork.ts | 9 +- .../e2e/doppelganger/doppelganger.test.ts | 4 +- .../test/e2e/network/reqresp.test.ts | 4 +- .../test/mocks/fork-choice/timeliness.ts | 4 +- .../beacon-node/test/sim/mergemock.test.ts | 4 +- .../test/spec/presets/fork_choice.test.ts | 8 +- .../test/spec/presets/genesis.test.ts | 8 +- .../presets/light_client/update_ranking.ts | 4 +- .../test/spec/presets/sanity.test.ts | 4 +- .../test/spec/presets/transition.test.ts | 6 +- .../test/spec/utils/expectEqualBeaconState.ts | 6 +- .../blocks/verifyBlocksSanityChecks.test.ts | 26 +- .../upgradeLightClientHeader.test.ts | 4 +- .../test/unit/chain/validation/block.test.ts | 4 +- .../collectSequentialBlocksInRange.test.ts | 4 +- .../beacon-node/test/utils/node/simTest.ts | 6 +- packages/beacon-node/test/utils/state.ts | 4 +- .../cli/test/utils/crucible/interfaces.ts | 4 +- .../cli/test/utils/crucible/utils/network.ts | 4 +- .../cli/test/utils/crucible/utils/syncing.ts | 5 +- packages/config/src/forkConfig/index.ts | 26 +- packages/config/src/forkConfig/types.ts | 14 +- .../fork-choice/src/forkChoice/forkChoice.ts | 6 +- .../fork-choice/src/forkChoice/interface.ts | 4 +- packages/light-client/src/events.ts | 6 +- packages/light-client/src/index.ts | 24 +- packages/light-client/src/spec/index.ts | 16 +- .../light-client/src/spec/isBetterUpdate.ts | 4 +- .../src/spec/processLightClientUpdate.ts | 4 +- packages/light-client/src/spec/store.ts | 26 +- packages/light-client/src/spec/utils.ts | 67 ++-- .../src/spec/validateLightClientBootstrap.ts | 4 +- .../src/spec/validateLightClientUpdate.ts | 4 +- .../light-client/src/transport/interface.ts | 20 +- packages/light-client/src/transport/rest.ts | 24 +- packages/light-client/src/types.ts | 6 +- packages/light-client/src/validation.ts | 8 +- .../unit/isValidLightClientHeader.test.ts | 6 +- packages/light-client/test/utils/utils.ts | 4 +- packages/params/src/forkName.ts | 2 + .../src/proof_provider/payload_store.ts | 17 +- .../src/proof_provider/proof_provider.ts | 10 +- packages/prover/src/utils/consensus.ts | 10 +- packages/prover/src/utils/evm.ts | 10 +- packages/prover/src/utils/validation.ts | 4 +- .../unit/proof_provider/payload_store.test.ts | 10 +- packages/state-transition/src/block/index.ts | 4 +- .../src/block/processBlockHeader.ts | 4 +- .../src/block/processExecutionPayload.ts | 9 +- .../src/block/processOperations.ts | 4 +- .../src/block/processRandao.ts | 8 +- packages/state-transition/src/cache/types.ts | 28 +- .../src/signatureSets/attesterSlashings.ts | 4 +- .../src/signatureSets/index.ts | 4 +- .../src/signatureSets/indexedAttestation.ts | 4 +- .../src/signatureSets/proposer.ts | 6 +- .../src/signatureSets/proposerSlashings.ts | 4 +- .../src/signatureSets/randao.ts | 9 +- .../src/signatureSets/voluntaryExits.ts | 4 +- .../state-transition/src/stateTransition.ts | 4 +- .../state-transition/src/util/blindedBlock.ts | 62 ++-- .../state-transition/src/util/blockRoot.ts | 22 +- packages/state-transition/src/util/epoch.ts | 6 +- .../state-transition/src/util/execution.ts | 58 ++-- .../state-transition/src/util/sszBytes.ts | 9 +- .../src/util/weakSubjectivity.ts | 2 +- .../test/perf/analyzeEpochs.ts | 2 +- packages/state-transition/test/perf/types.ts | 4 +- packages/state-transition/test/perf/util.ts | 5 +- .../test/utils/testFileCache.ts | 4 +- packages/types/README.md | 6 +- packages/types/package.json | 3 - packages/types/src/allForks/index.ts | 7 - packages/types/src/allForks/sszTypes.ts | 160 ---------- packages/types/src/allForks/types.ts | 298 ------------------ packages/types/src/deneb/types.ts | 5 +- packages/types/src/index.ts | 4 +- packages/types/src/sszTypes.ts | 196 +++++++++++- packages/types/src/types.ts | 177 ++++++++++- packages/types/src/utils/typeguards.ts | 79 +++-- packages/validator/src/services/block.ts | 32 +- .../validator/src/services/validatorStore.ts | 11 +- .../src/util/externalSignerClient.ts | 6 +- 144 files changed, 1273 insertions(+), 1209 deletions(-) delete mode 100644 packages/types/src/allForks/index.ts delete mode 100644 packages/types/src/allForks/sszTypes.ts delete mode 100644 packages/types/src/allForks/types.ts diff --git a/packages/api/src/beacon/routes/beacon/block.ts b/packages/api/src/beacon/routes/beacon/block.ts index 76c2b3664a3f..dcf0d07c7a9b 100644 --- a/packages/api/src/beacon/routes/beacon/block.ts +++ b/packages/api/src/beacon/routes/beacon/block.ts @@ -1,7 +1,17 @@ /* eslint-disable @typescript-eslint/naming-convention */ import {ContainerType, ListCompositeType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {allForks, Slot, ssz, RootHex, deneb, isSignedBlockContents} from "@lodestar/types"; +import { + Slot, + ssz, + RootHex, + deneb, + isSignedBlockContents, + SignedBeaconBlock, + BeaconBlockBody, + SignedBeaconBlockOrContents, + SignedBlindedBeaconBlock, +} from "@lodestar/types"; import {ForkName, ForkSeq} from "@lodestar/params"; import {Endpoint, RequestCodec, RouteDefinitions, Schema} from "../../../utils/index.js"; import {EmptyMeta, EmptyResponseCodec, EmptyResponseData, WithVersion} from "../../../utils/codecs.js"; @@ -74,7 +84,7 @@ export type Endpoints = { "GET", BlockArgs, {params: {block_id: string}}, - allForks.SignedBeaconBlock, + SignedBeaconBlock, ExecutionOptimisticFinalizedAndVersionMeta >; @@ -86,7 +96,7 @@ export type Endpoints = { "GET", BlockArgs, {params: {block_id: string}}, - allForks.BeaconBlockBody["attestations"], + BeaconBlockBody["attestations"], ExecutionOptimisticAndFinalizedMeta >; @@ -139,7 +149,7 @@ export type Endpoints = { */ publishBlock: Endpoint< "POST", - {signedBlockOrContents: allForks.SignedBeaconBlockOrContents}, + {signedBlockOrContents: SignedBeaconBlockOrContents}, {body: unknown; headers: {[MetaHeader.Version]: string}}, EmptyResponseData, EmptyMeta @@ -148,7 +158,7 @@ export type Endpoints = { publishBlockV2: Endpoint< "POST", { - signedBlockOrContents: allForks.SignedBeaconBlockOrContents; + signedBlockOrContents: SignedBeaconBlockOrContents; broadcastValidation?: BroadcastValidation; }, {body: unknown; headers: {[MetaHeader.Version]: string}; query: {broadcast_validation?: string}}, @@ -162,7 +172,7 @@ export type Endpoints = { */ publishBlindedBlock: Endpoint< "POST", - {signedBlindedBlock: allForks.SignedBlindedBeaconBlock}, + {signedBlindedBlock: SignedBlindedBeaconBlock}, {body: unknown; headers: {[MetaHeader.Version]: string}}, EmptyResponseData, EmptyMeta @@ -171,7 +181,7 @@ export type Endpoints = { publishBlindedBlockV2: Endpoint< "POST", { - signedBlindedBlock: allForks.SignedBlindedBeaconBlock; + signedBlindedBlock: SignedBlindedBeaconBlock; broadcastValidation?: BroadcastValidation; }, {body: unknown; headers: {[MetaHeader.Version]: string}; query: {broadcast_validation?: string}}, @@ -267,9 +277,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions; }; @@ -136,7 +136,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ssz[fork].BeaconState as Type), + data: WithVersion((fork) => ssz[fork].BeaconState as Type), meta: ExecutionOptimisticFinalizedAndVersionCodec, }, init: { diff --git a/packages/api/src/beacon/routes/events.ts b/packages/api/src/beacon/routes/events.ts index 0b88175d7588..23be5e7c2288 100644 --- a/packages/api/src/beacon/routes/events.ts +++ b/packages/api/src/beacon/routes/events.ts @@ -1,6 +1,19 @@ import {ContainerType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {Epoch, phase0, capella, Slot, ssz, StringType, RootHex, altair, UintNum64, allForks} from "@lodestar/types"; +import { + Epoch, + phase0, + capella, + Slot, + ssz, + StringType, + RootHex, + altair, + UintNum64, + LightClientOptimisticUpdate, + LightClientFinalityUpdate, + SSEPayloadAttributes, +} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; @@ -113,9 +126,9 @@ export type EventData = { executionOptimistic: boolean; }; [EventType.contributionAndProof]: altair.SignedContributionAndProof; - [EventType.lightClientOptimisticUpdate]: {version: ForkName; data: allForks.LightClientOptimisticUpdate}; - [EventType.lightClientFinalityUpdate]: {version: ForkName; data: allForks.LightClientFinalityUpdate}; - [EventType.payloadAttributes]: {version: ForkName; data: allForks.SSEPayloadAttributes}; + [EventType.lightClientOptimisticUpdate]: {version: ForkName; data: LightClientOptimisticUpdate}; + [EventType.lightClientFinalityUpdate]: {version: ForkName; data: LightClientFinalityUpdate}; + [EventType.payloadAttributes]: {version: ForkName; data: SSEPayloadAttributes}; [EventType.blobSidecar]: BlobSidecarSSE; }; diff --git a/packages/api/src/beacon/routes/lightclient.ts b/packages/api/src/beacon/routes/lightclient.ts index ed5e290a747c..ab45323f8f64 100644 --- a/packages/api/src/beacon/routes/lightclient.ts +++ b/packages/api/src/beacon/routes/lightclient.ts @@ -1,6 +1,13 @@ /* eslint-disable @typescript-eslint/naming-convention */ import {ListCompositeType, ValueOf} from "@chainsafe/ssz"; -import {ssz, SyncPeriod, allForks} from "@lodestar/types"; +import { + LightClientBootstrap, + LightClientFinalityUpdate, + LightClientOptimisticUpdate, + LightClientUpdate, + ssz, + SyncPeriod, +} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; @@ -33,7 +40,7 @@ export type Endpoints = { "GET", {startPeriod: SyncPeriod; count: number}, {query: {start_period: number; count: number}}, - allForks.LightClientUpdate[], + LightClientUpdate[], {versions: ForkName[]} >; @@ -46,7 +53,7 @@ export type Endpoints = { "GET", EmptyArgs, EmptyRequest, - allForks.LightClientOptimisticUpdate, + LightClientOptimisticUpdate, VersionMeta >; @@ -55,7 +62,7 @@ export type Endpoints = { "GET", EmptyArgs, EmptyRequest, - allForks.LightClientFinalityUpdate, + LightClientFinalityUpdate, VersionMeta >; @@ -68,7 +75,7 @@ export type Endpoints = { "GET", {blockRoot: string}, {params: {block_root: string}}, - allForks.LightClientBootstrap, + LightClientBootstrap, VersionMeta >; @@ -105,7 +112,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { const updates = data as unknown[]; - const value: allForks.LightClientUpdate[] = []; + const value: LightClientUpdate[] = []; for (let i = 0; i < updates.length; i++) { const version = meta.versions[i]; value.push(getLightClientForkTypes(version).LightClientUpdate.fromJson(updates[i])); @@ -132,9 +139,9 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions; @@ -349,7 +350,7 @@ export type Endpoints = { blinded_local?: boolean; }; }, - allForks.FullOrBlindedBeaconBlockOrContents, + BeaconBlockOrContents | BlindedBeaconBlock, ProduceBlockV3Meta >; @@ -361,7 +362,7 @@ export type Endpoints = { graffiti: string; }, {params: {slot: number}; query: {randao_reveal: string; graffiti: string}}, - allForks.BlindedBeaconBlock, + BlindedBeaconBlock, VersionMeta >; @@ -623,8 +624,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions - (isForkBlobs(fork) ? BlockContentsType : ssz[fork].BeaconBlock) as Type + (fork) => (isForkBlobs(fork) ? BlockContentsType : ssz[fork].BeaconBlock) as Type ), meta: VersionCodec, }, @@ -688,7 +688,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions + : ssz[version].BeaconBlock) as Type ), meta: { toJson: (meta) => ({ diff --git a/packages/api/src/builder/routes.ts b/packages/api/src/builder/routes.ts index d703085583da..7e6a6e24b1b7 100644 --- a/packages/api/src/builder/routes.ts +++ b/packages/api/src/builder/routes.ts @@ -1,6 +1,16 @@ /* eslint-disable @typescript-eslint/naming-convention */ import {fromHexString, toHexString} from "@chainsafe/ssz"; -import {ssz, allForks, bellatrix, Slot, Root, BLSPubkey} from "@lodestar/types"; +import { + ssz, + bellatrix, + Slot, + Root, + BLSPubkey, + ExecutionPayload, + ExecutionPayloadAndBlobsBundle, + SignedBlindedBeaconBlock, + SignedBuilderBid, +} from "@lodestar/types"; import {ForkName, isForkBlobs} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; @@ -26,7 +36,7 @@ import {fromHeaders} from "../utils/headers.js"; // In this case, we receive a success response (204) which is not handled as an error. The generic response // handler already checks the status code and will not attempt to parse the body, but it will return no value. // It is important that this type indicates that there might be no value to ensure it is properly handled downstream. -export type MaybeSignedBuilderBid = allForks.SignedBuilderBid | undefined; +export type MaybeSignedBuilderBid = SignedBuilderBid | undefined; const RegistrationsType = ArrayOf(ssz.bellatrix.SignedValidatorRegistrationV1); @@ -62,9 +72,9 @@ export type Endpoints = { submitBlindedBlock: Endpoint< "POST", - {signedBlindedBlock: allForks.SignedBlindedBeaconBlock}, + {signedBlindedBlock: SignedBlindedBeaconBlock}, {body: unknown; headers: {[MetaHeader.Version]: string}}, - allForks.ExecutionPayload | allForks.ExecutionPayloadAndBlobsBundle, + ExecutionPayload | ExecutionPayloadAndBlobsBundle, VersionMeta >; }; @@ -138,13 +148,11 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions( - (fork: ForkName) => { - return isForkBlobs(fork) - ? ssz.allForksBlobs[fork].ExecutionPayloadAndBlobsBundle - : getExecutionForkTypes(fork).ExecutionPayload; - } - ), + data: WithVersion((fork: ForkName) => { + return isForkBlobs(fork) + ? ssz.allForksBlobs[fork].ExecutionPayloadAndBlobsBundle + : getExecutionForkTypes(fork).ExecutionPayload; + }), meta: VersionCodec, }, }, diff --git a/packages/api/src/utils/fork.ts b/packages/api/src/utils/fork.ts index 290147e7e47a..0925e750719a 100644 --- a/packages/api/src/utils/fork.ts +++ b/packages/api/src/utils/fork.ts @@ -1,5 +1,13 @@ -import {ForkName, isForkBlobs, isForkExecution, isForkLightClient} from "@lodestar/params"; -import {allForks, ssz} from "@lodestar/types"; +import { + ForkBlobs, + ForkExecution, + ForkLightClient, + ForkName, + isForkBlobs, + isForkExecution, + isForkLightClient, +} from "@lodestar/params"; +import {SSZBlindedTypesFor, SSZTypesFor, ssz, sszTypesFor} from "@lodestar/types"; export function toForkName(version: string): ForkName { // Teku returns fork as UPPERCASE @@ -11,30 +19,33 @@ export function toForkName(version: string): ForkName { return version as ForkName; } -export function getLightClientForkTypes(fork: ForkName): allForks.AllForksLightClientSSZTypes { +export function getLightClientForkTypes(fork: ForkName): SSZTypesFor { if (!isForkLightClient(fork)) { throw Error(`Invalid fork=${fork} for lightclient fork types`); } - return ssz.allForksLightClient[fork]; + + return sszTypesFor(fork); } -export function getExecutionForkTypes(fork: ForkName): allForks.AllForksExecutionSSZTypes { +export function getExecutionForkTypes(fork: ForkName): SSZTypesFor { if (!isForkExecution(fork)) { throw Error(`Invalid fork=${fork} for execution fork types`); } - return ssz.allForksExecution[fork]; + + return sszTypesFor(fork); } -export function getBlindedForkTypes(fork: ForkName): allForks.AllForksBlindedSSZTypes { +export function getBlindedForkTypes(fork: ForkName): SSZBlindedTypesFor { if (!isForkExecution(fork)) { throw Error(`Invalid fork=${fork} for blinded fork types`); } - return ssz.allForksBlinded[fork] as allForks.AllForksBlindedSSZTypes; + return ssz.allForksBlinded[fork]; } -export function getBlobsForkTypes(fork: ForkName): allForks.AllForksBlobsSSZTypes { +export function getBlobsForkTypes(fork: ForkName): SSZTypesFor { if (!isForkBlobs(fork)) { throw Error(`Invalid fork=${fork} for blobs fork types`); } - return ssz.allForksBlobs[fork]; + + return sszTypesFor(fork); } diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index c7dde0077944..a6b48320bf8c 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -1,6 +1,6 @@ import {toHexString} from "@chainsafe/ssz"; import {ForkName} from "@lodestar/params"; -import {Slot, allForks, ssz} from "@lodestar/types"; +import {SignedBlindedBeaconBlock, Slot, ssz} from "@lodestar/types"; import { BlockHeaderResponse, BroadcastValidation, @@ -242,7 +242,7 @@ export const testData: GenericServerTestCases = { }, }; -function getDefaultBlindedBlock(slot: Slot): allForks.SignedBlindedBeaconBlock { +function getDefaultBlindedBlock(slot: Slot): SignedBlindedBeaconBlock { const block = ssz.bellatrix.SignedBlindedBeaconBlock.defaultValue(); block.message.slot = slot; return block; 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 dc85daff8575..d4675de21185 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -3,7 +3,14 @@ import {ApplicationMethods} from "@lodestar/api/server"; import {computeEpochAtSlot, computeTimeAtSlot, reconstructFullBlockOrContents} from "@lodestar/state-transition"; import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; import {sleep, fromHex, toHex} from "@lodestar/utils"; -import {allForks, deneb, isSignedBlockContents, ProducedBlockSource} from "@lodestar/types"; +import { + deneb, + isSignedBlockContents, + ProducedBlockSource, + SignedBeaconBlock, + SignedBeaconBlockOrContents, + SignedBlindedBeaconBlock, +} from "@lodestar/types"; import { BlockSource, getBlockInput, @@ -53,7 +60,7 @@ export function getBeaconBlockApi({ opts: PublishBlockOpts = {} ) => { const seenTimestampSec = Date.now() / 1000; - let blockForImport: BlockInput, signedBlock: allForks.SignedBeaconBlock, blobSidecars: deneb.BlobSidecars; + let blockForImport: BlockInput, signedBlock: SignedBeaconBlock, blobSidecars: deneb.BlobSidecars; if (isSignedBlockContents(signedBlockOrContents)) { ({signedBlock} = signedBlockOrContents); @@ -463,8 +470,8 @@ export function getBeaconBlockApi({ async function reconstructBuilderBlockOrContents( chain: ApiModules["chain"], - signedBlindedBlock: allForks.SignedBlindedBeaconBlock -): Promise { + signedBlindedBlock: SignedBlindedBeaconBlock +): Promise { const executionBuilder = chain.executionBuilder; if (!executionBuilder) { throw Error("executionBuilder required to publish SignedBlindedBeaconBlock"); 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 e69e4fa74419..f0d243967c22 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,7 @@ -import {allForks} from "@lodestar/types"; import {routes} from "@lodestar/api"; import {blockToHeader} from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; +import {SignedBeaconBlock} from "@lodestar/types"; import {GENESIS_SLOT} from "../../../../constants/index.js"; import {ApiError, ValidationError} from "../../errors.js"; import {IBeaconChain} from "../../../../chain/interface.js"; @@ -9,7 +9,7 @@ import {rootHexRegex} from "../../../../eth1/provider/utils.js"; export function toBeaconHeaderResponse( config: ChainForkConfig, - block: allForks.SignedBeaconBlock, + block: SignedBeaconBlock, canonical = false ): routes.beacon.BlockHeaderResponse { return { @@ -25,7 +25,7 @@ export function toBeaconHeaderResponse( export async function resolveBlockId( chain: IBeaconChain, blockId: routes.beacon.BlockId -): Promise<{block: allForks.SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean}> { +): 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}'`); @@ -37,7 +37,7 @@ export async function resolveBlockId( async function resolveBlockIdOrNull( chain: IBeaconChain, blockId: routes.beacon.BlockId -): Promise<{block: allForks.SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null> { +): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null> { blockId = String(blockId).toLowerCase(); if (blockId === "head") { return chain.getBlockByRoot(chain.forkChoice.getHead().blockRoot); diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index a74c9b2ec8cb..8fb40e4ecb6b 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -31,12 +31,14 @@ import { Epoch, ProducedBlockSource, bellatrix, - allForks, BLSSignature, isBlindedBeaconBlock, isBlockContents, phase0, Wei, + BeaconBlock, + BlockContents, + BlindedBeaconBlock, } from "@lodestar/types"; import {ExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {fromHex, toHex, resolveOrRacePromises, prettyWeiToEth} from "@lodestar/utils"; @@ -91,11 +93,11 @@ const BLOCK_PRODUCTION_RACE_CUTOFF_MS = 2_000; const BLOCK_PRODUCTION_RACE_TIMEOUT_MS = 12_000; type ProduceBlockOrContentsRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & ( - | {data: allForks.BeaconBlock; version: ForkPreBlobs} - | {data: allForks.BlockContents; version: ForkBlobs} + | {data: BeaconBlock; version: ForkPreBlobs} + | {data: BlockContents; version: ForkBlobs} ); type ProduceBlindedBlockRes = {executionPayloadValue: Wei; consensusBlockValue: Wei} & { - data: allForks.BlindedBeaconBlock; + data: BlindedBeaconBlock; version: ForkExecution; }; @@ -503,7 +505,7 @@ export function getValidatorApi({ } return { - data: {block, ...contents} as allForks.BlockContents, + data: {block, ...contents} as BlockContents, version, executionPayloadValue, consensusBlockValue, @@ -760,13 +762,13 @@ export function getValidatorApi({ } else { if (isBlockContents(data)) { const {block} = data; - const blindedBlock = beaconBlockToBlinded(config, block as allForks.AllForksExecution["BeaconBlock"]); + const blindedBlock = beaconBlockToBlinded(config, block as BeaconBlock); return { data: blindedBlock, meta: {...meta, executionPayloadBlinded: true}, }; } else { - const blindedBlock = beaconBlockToBlinded(config, data as allForks.AllForksExecution["BeaconBlock"]); + const blindedBlock = beaconBlockToBlinded(config, data as BeaconBlock); return { data: blindedBlock, meta: {...meta, executionPayloadBlinded: true}, @@ -786,12 +788,12 @@ export function getValidatorApi({ if (isBlockContents(data)) { const {block} = data; - const blindedBlock = beaconBlockToBlinded(config, block as allForks.AllForksExecution["BeaconBlock"]); + const blindedBlock = beaconBlockToBlinded(config, block as BeaconBlock); return {data: blindedBlock, meta: {version}}; } else if (isBlindedBeaconBlock(data)) { return {data, meta: {version}}; } else { - const blindedBlock = beaconBlockToBlinded(config, data as allForks.AllForksExecution["BeaconBlock"]); + const blindedBlock = beaconBlockToBlinded(config, data as BeaconBlock); return {data: blindedBlock, meta: {version}}; } }, diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index 9c467c26ca50..e67b6d1e9dbc 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -1,6 +1,6 @@ import {toHexString} from "@chainsafe/ssz"; -import {capella, ssz, allForks, altair} from "@lodestar/types"; -import {ForkSeq, INTERVALS_PER_SLOT, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {capella, ssz, altair, BeaconBlock} from "@lodestar/types"; +import {ForkLightClient, ForkSeq, INTERVALS_PER_SLOT, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params"; import { CachedBeaconStateAltair, computeEpochAtSlot, @@ -306,7 +306,7 @@ export async function importBlock( callInNextEventLoop(() => { try { this.lightClientServer.onImportBlockHead( - block.message as allForks.AllForksLightClient["BeaconBlock"], + block.message as BeaconBlock, postState as CachedBeaconStateAltair, parentBlockSlot ); diff --git a/packages/beacon-node/src/chain/blocks/index.ts b/packages/beacon-node/src/chain/blocks/index.ts index 083ab6bd4471..eb8c2663c9b6 100644 --- a/packages/beacon-node/src/chain/blocks/index.ts +++ b/packages/beacon-node/src/chain/blocks/index.ts @@ -1,5 +1,5 @@ -import {allForks} from "@lodestar/types"; import {toHex, isErrorAborted} from "@lodestar/utils"; +import {SignedBeaconBlock} from "@lodestar/types"; import {JobItemQueue, isQueueErrorAborted} from "../../util/queue/index.js"; import {Metrics} from "../../metrics/metrics.js"; import {BlockError, BlockErrorCode, isBlockErrorAborted} from "../errors/index.js"; @@ -158,7 +158,7 @@ export async function processBlocks( } } -function getBlockError(e: unknown, block: allForks.SignedBeaconBlock): BlockError { +function getBlockError(e: unknown, block: SignedBeaconBlock): BlockError { if (e instanceof BlockError) { return e; } else if (e instanceof Error) { diff --git a/packages/beacon-node/src/chain/blocks/types.ts b/packages/beacon-node/src/chain/blocks/types.ts index 2996bac7887f..da573bb76334 100644 --- a/packages/beacon-node/src/chain/blocks/types.ts +++ b/packages/beacon-node/src/chain/blocks/types.ts @@ -1,6 +1,6 @@ import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition"; import {MaybeValidExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice"; -import {allForks, deneb, Slot, RootHex} from "@lodestar/types"; +import {deneb, Slot, RootHex, SignedBeaconBlock} from "@lodestar/types"; import {ForkSeq, ForkName} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; @@ -47,7 +47,7 @@ type Availability = {availabilityPromise: Promise; resolveAvailability: (d type CachedBlobs = {blobsCache: BlobsCacheMap} & Availability; export type CachedData = ForkBlobsInfo & CachedBlobs; -export type BlockInput = {block: allForks.SignedBeaconBlock; source: BlockSource; blockBytes: Uint8Array | null} & ( +export type BlockInput = {block: SignedBeaconBlock; source: BlockSource; blockBytes: Uint8Array | null} & ( | {type: BlockInputType.preData | BlockInputType.outOfRangeData} | ({type: BlockInputType.availableData} & {blockData: BlockInputData}) // the blobsSource here is added to BlockInputBlobs when availability is resolved @@ -68,7 +68,7 @@ export function blockRequiresBlobs(config: ChainForkConfig, blockSlot: Slot, clo export const getBlockInput = { preData( config: ChainForkConfig, - block: allForks.SignedBeaconBlock, + block: SignedBeaconBlock, source: BlockSource, blockBytes: Uint8Array | null ): BlockInput { @@ -91,7 +91,7 @@ export const getBlockInput = { // building states or where importing data isn't important if valid child exists like ILs outOfRangeData( config: ChainForkConfig, - block: allForks.SignedBeaconBlock, + block: SignedBeaconBlock, source: BlockSource, blockBytes: Uint8Array | null ): BlockInput { @@ -108,7 +108,7 @@ export const getBlockInput = { availableData( config: ChainForkConfig, - block: allForks.SignedBeaconBlock, + block: SignedBeaconBlock, source: BlockSource, blockBytes: Uint8Array | null, blockData: BlockInputData @@ -127,7 +127,7 @@ export const getBlockInput = { dataPromise( config: ChainForkConfig, - block: allForks.SignedBeaconBlock, + block: SignedBeaconBlock, source: BlockSource, blockBytes: Uint8Array | null, cachedData: CachedData diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts index ed3e27551b88..d08a747259b1 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts @@ -6,7 +6,7 @@ import { isMergeTransitionBlock as isMergeTransitionBlockFn, isExecutionEnabled, } from "@lodestar/state-transition"; -import {bellatrix, allForks, Slot, deneb} from "@lodestar/types"; +import {bellatrix, Slot, deneb, SignedBeaconBlock} from "@lodestar/types"; import { IForkChoice, assertValidTerminalPowBlock, @@ -68,7 +68,7 @@ type VerifyBlockExecutionResponse = export async function verifyBlocksExecutionPayload( chain: VerifyBlockExecutionPayloadModules, parentBlock: ProtoBlock, - blocks: allForks.SignedBeaconBlock[], + blocks: SignedBeaconBlock[], preState0: CachedBeaconStateAllForks, signal: AbortSignal, opts: BlockProcessOpts & ImportBlockOpts @@ -274,7 +274,7 @@ export async function verifyBlocksExecutionPayload( */ export async function verifyBlockExecutionPayload( chain: VerifyBlockExecutionPayloadModules, - block: allForks.SignedBeaconBlock, + block: SignedBeaconBlock, preState0: CachedBeaconStateAllForks, opts: BlockProcessOpts, isOptimisticallySafe: boolean, @@ -393,7 +393,7 @@ export async function verifyBlockExecutionPayload( function getSegmentErrorResponse( {verifyResponse, blockIndex}: {verifyResponse: VerifyExecutionErrorResponse; blockIndex: number}, parentBlock: ProtoBlock, - blocks: allForks.SignedBeaconBlock[] + blocks: SignedBeaconBlock[] ): SegmentExecStatus { const {executionStatus, lvhResponse, execError} = verifyResponse; let invalidSegmentLVH: LVHInvalidResponse | undefined = undefined; diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksSignatures.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksSignatures.ts index 6c66b5d74c4c..e86549cda7d3 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksSignatures.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksSignatures.ts @@ -1,6 +1,6 @@ import {CachedBeaconStateAllForks, getBlockSignatureSets} from "@lodestar/state-transition"; -import {allForks} from "@lodestar/types"; import {Logger} from "@lodestar/utils"; +import {SignedBeaconBlock} from "@lodestar/types"; import {Metrics} from "../../metrics/metrics.js"; import {IBlsVerifier} from "../bls/index.js"; import {BlockError, BlockErrorCode} from "../errors/blockError.js"; @@ -19,7 +19,7 @@ export async function verifyBlocksSignatures( logger: Logger, metrics: Metrics | null, preState0: CachedBeaconStateAllForks, - blocks: allForks.SignedBeaconBlock[], + blocks: SignedBeaconBlock[], opts: ImportBlockOpts ): Promise<{verifySignaturesTime: number}> { const isValidPromises: Promise[] = []; @@ -37,7 +37,9 @@ export async function verifyBlocksSignatures( : // // Verify signatures per block to track which block is invalid bls.verifySignatureSets( - getBlockSignatureSets(preState0, block, {skipProposerSignature: opts.validProposerSignature}) + getBlockSignatureSets(preState0, block, { + skipProposerSignature: opts.validProposerSignature, + }) ); // getBlockSignatureSets() takes 45ms in benchmarks for 2022Q2 mainnet blocks (100 sigs). When syncing a 32 blocks diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 2f58962f3cc5..ccd10f7b4d6f 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -16,7 +16,6 @@ import { } from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; import { - allForks, UintNum64, Root, phase0, @@ -28,6 +27,11 @@ import { Wei, bellatrix, isBlindedBeaconBlock, + BeaconBlock, + SignedBeaconBlock, + ExecutionPayload, + BlindedBeaconBlock, + BlindedBeaconBlockBody, } from "@lodestar/types"; import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice"; import {ProcessShutdownCallback} from "@lodestar/validator"; @@ -157,7 +161,7 @@ export class BeaconChain implements IBeaconChain { // Cache payload from the local execution so that produceBlindedBlock or produceBlockV3 and // send and get signed/published blinded versions which beacon can assemble into full before // actual publish - readonly producedBlockRoot = new Map(); + readonly producedBlockRoot = new Map(); readonly producedBlindedBlockRoot = new Set(); readonly opts: IChainOptions; @@ -498,7 +502,7 @@ export class BeaconChain implements IBeaconChain { async getCanonicalBlockAtSlot( slot: Slot - ): Promise<{block: allForks.SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null> { + ): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null> { const finalizedBlock = this.forkChoice.getFinalizedBlock(); if (slot > finalizedBlock.slot) { // Unfinalized slot, attempt to find in fork-choice @@ -520,7 +524,7 @@ export class BeaconChain implements IBeaconChain { async getBlockByRoot( root: string - ): Promise<{block: allForks.SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null> { + ): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null> { const block = this.forkChoice.getBlockHex(root); if (block) { const data = await this.db.block.get(fromHexString(root)); @@ -554,7 +558,7 @@ export class BeaconChain implements IBeaconChain { } produceBlock(blockAttributes: BlockAttributes & {commonBlockBody?: CommonBlockBody}): Promise<{ - block: allForks.BeaconBlock; + block: BeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Wei; shouldOverrideBuilder?: boolean; @@ -563,7 +567,7 @@ export class BeaconChain implements IBeaconChain { } produceBlindedBlock(blockAttributes: BlockAttributes & {commonBlockBody?: CommonBlockBody}): Promise<{ - block: allForks.BlindedBeaconBlock; + block: BlindedBeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Wei; }> { @@ -616,7 +620,7 @@ export class BeaconChain implements IBeaconChain { const bodyRoot = blockType === BlockType.Full ? this.config.getForkTypes(slot).BeaconBlockBody.hashTreeRoot(body) - : this.config.getBlindedForkTypes(slot).BeaconBlockBody.hashTreeRoot(body as allForks.BlindedBeaconBlockBody); + : this.config.getBlindedForkTypes(slot).BeaconBlockBody.hashTreeRoot(body as BlindedBeaconBlockBody); this.logger.debug("Computing block post state from the produced body", { slot, bodyRoot: toHexString(bodyRoot), @@ -636,7 +640,7 @@ export class BeaconChain implements IBeaconChain { const blockRoot = blockType === BlockType.Full ? this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block) - : this.config.getBlindedForkTypes(slot).BeaconBlock.hashTreeRoot(block as allForks.BlindedBeaconBlock); + : this.config.getBlindedForkTypes(slot).BeaconBlock.hashTreeRoot(block as BlindedBeaconBlock); const blockRootHex = toHex(blockRoot); // track the produced block for consensus broadcast validations @@ -770,7 +774,7 @@ export class BeaconChain implements IBeaconChain { return this.reprocessController.waitForBlockOfAttestation(slot, root); } - persistBlock(data: allForks.BeaconBlock | allForks.BlindedBeaconBlock, suffix?: string): void { + persistBlock(data: BeaconBlock | BlindedBeaconBlock, suffix?: string): void { const slot = data.slot; if (isBlindedBeaconBlock(data)) { const sszType = this.config.getBlindedForkTypes(slot).BeaconBlock; @@ -1094,7 +1098,7 @@ export class BeaconChain implements IBeaconChain { } } - async getBlockRewards(block: allForks.FullOrBlindedBeaconBlock): Promise { + async getBlockRewards(block: BeaconBlock | BlindedBeaconBlock): Promise { const preState = this.regen.getPreStateSync(block); if (preState === null) { @@ -1133,7 +1137,7 @@ export class BeaconChain implements IBeaconChain { } async getSyncCommitteeRewards( - block: allForks.FullOrBlindedBeaconBlock, + block: BeaconBlock | BlindedBeaconBlock, validatorIds?: (ValidatorIndex | string)[] ): Promise { const preState = this.regen.getPreStateSync(block); diff --git a/packages/beacon-node/src/chain/errors/blockError.ts b/packages/beacon-node/src/chain/errors/blockError.ts index 6ab15275934e..5f12bd939342 100644 --- a/packages/beacon-node/src/chain/errors/blockError.ts +++ b/packages/beacon-node/src/chain/errors/blockError.ts @@ -1,5 +1,5 @@ import {toHexString} from "@chainsafe/ssz"; -import {allForks, RootHex, Slot, ValidatorIndex} from "@lodestar/types"; +import {RootHex, SignedBeaconBlock, Slot, ValidatorIndex} from "@lodestar/types"; import {LodestarError} from "@lodestar/utils"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {ExecutionPayloadStatus} from "../../execution/engine/interface.js"; @@ -112,7 +112,7 @@ export class BlockGossipError extends GossipActionError {} export class BlockError extends LodestarError { constructor( - readonly signedBlock: allForks.SignedBeaconBlock, + readonly signedBlock: SignedBeaconBlock, type: BlockErrorType ) { super(type); diff --git a/packages/beacon-node/src/chain/initState.ts b/packages/beacon-node/src/chain/initState.ts index 20a2188136b5..aae03a07f50c 100644 --- a/packages/beacon-node/src/chain/initState.ts +++ b/packages/beacon-node/src/chain/initState.ts @@ -7,7 +7,7 @@ import { computeCheckpointEpochAtStateSlot, computeStartSlotAtEpoch, } from "@lodestar/state-transition"; -import {phase0, allForks, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {Logger, toHex} from "@lodestar/utils"; import {GENESIS_SLOT, ZERO_HASH} from "../constants/index.js"; @@ -21,7 +21,7 @@ import {GenesisResult} from "./genesis/interface.js"; export async function persistGenesisResult( db: IBeaconDb, genesisResult: GenesisResult, - genesisBlock: allForks.SignedBeaconBlock + genesisBlock: SignedBeaconBlock ): Promise { await Promise.all([ db.stateArchive.add(genesisResult.state), @@ -52,10 +52,7 @@ export async function persistAnchorState( } } -export function createGenesisBlock( - config: ChainForkConfig, - genesisState: BeaconStateAllForks -): allForks.SignedBeaconBlock { +export function createGenesisBlock(config: ChainForkConfig, genesisState: BeaconStateAllForks): SignedBeaconBlock { const types = config.getForkTypes(GENESIS_SLOT); const genesisBlock = types.SignedBeaconBlock.defaultValue(); const stateRoot = genesisState.hashTreeRoot(); diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index af7aeb47dd9c..e412d8e8aafa 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -1,6 +1,5 @@ import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz"; import { - allForks, UintNum64, Root, phase0, @@ -12,6 +11,10 @@ import { Wei, capella, altair, + BeaconBlock, + ExecutionPayload, + SignedBeaconBlock, + BlindedBeaconBlock, } from "@lodestar/types"; import { BeaconStateAllForks, @@ -22,7 +25,6 @@ import { } from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; import {Logger} from "@lodestar/utils"; - import {CheckpointWithHex, IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; import {IEth1ForBlockProduction} from "../eth1/index.js"; import {IExecutionEngine, IExecutionBuilder} from "../execution/index.js"; @@ -120,7 +122,7 @@ export interface IBeaconChain { readonly beaconProposerCache: BeaconProposerCache; readonly checkpointBalancesCache: CheckpointBalancesCache; readonly producedContentsCache: Map; - readonly producedBlockRoot: Map; + readonly producedBlockRoot: Map; readonly shufflingCache: ShufflingCache; readonly producedBlindedBlockRoot: Set; readonly opts: IChainOptions; @@ -162,25 +164,25 @@ export interface IBeaconChain { */ getCanonicalBlockAtSlot( slot: Slot - ): Promise<{block: allForks.SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null>; + ): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null>; /** * Get local block by root, does not fetch from the network */ getBlockByRoot( root: RootHex - ): Promise<{block: allForks.SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null>; + ): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null>; getContents(beaconBlock: deneb.BeaconBlock): deneb.Contents; produceCommonBlockBody(blockAttributes: BlockAttributes): Promise; produceBlock(blockAttributes: BlockAttributes & {commonBlockBody?: CommonBlockBody}): Promise<{ - block: allForks.BeaconBlock; + block: BeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Wei; shouldOverrideBuilder?: boolean; }>; produceBlindedBlock(blockAttributes: BlockAttributes & {commonBlockBody?: CommonBlockBody}): Promise<{ - block: allForks.BlindedBeaconBlock; + block: BlindedBeaconBlock; executionPayloadValue: Wei; consensusBlockValue: Wei; }>; @@ -204,7 +206,7 @@ export interface IBeaconChain { updateBeaconProposerData(epoch: Epoch, proposers: ProposerPreparationData[]): Promise; - persistBlock(data: allForks.BeaconBlock | allForks.BlindedBeaconBlock, suffix?: string): void; + persistBlock(data: BeaconBlock | BlindedBeaconBlock, suffix?: string): void; persistInvalidSszValue(type: Type, sszObject: T | Uint8Array, suffix?: string): void; persistInvalidSszBytes(type: string, sszBytes: Uint8Array, suffix?: string): void; /** Persist bad items to persistInvalidSszObjectsDir dir, for example invalid state, attestations etc. */ @@ -220,13 +222,13 @@ export interface IBeaconChain { regenCanAcceptWork(): boolean; blsThreadPoolCanAcceptWork(): boolean; - getBlockRewards(blockRef: allForks.FullOrBlindedBeaconBlock): Promise; + getBlockRewards(blockRef: BeaconBlock | BlindedBeaconBlock): Promise; getAttestationsRewards( epoch: Epoch, validatorIds?: (ValidatorIndex | string)[] ): Promise<{rewards: AttestationsRewards; executionOptimistic: boolean; finalized: boolean}>; getSyncCommitteeRewards( - blockRef: allForks.FullOrBlindedBeaconBlock, + blockRef: BeaconBlock | BlindedBeaconBlock, validatorIds?: (ValidatorIndex | string)[] ): Promise; } diff --git a/packages/beacon-node/src/chain/lightClient/index.ts b/packages/beacon-node/src/chain/lightClient/index.ts index 75d094036f2d..5d25985df66e 100644 --- a/packages/beacon-node/src/chain/lightClient/index.ts +++ b/packages/beacon-node/src/chain/lightClient/index.ts @@ -1,5 +1,21 @@ import {BitArray, CompositeViewDU, toHexString} from "@chainsafe/ssz"; -import {altair, phase0, Root, RootHex, Slot, ssz, SyncPeriod, allForks} from "@lodestar/types"; +import { + altair, + BeaconBlock, + BeaconBlockBody, + LightClientBootstrap, + LightClientFinalityUpdate, + LightClientHeader, + LightClientOptimisticUpdate, + LightClientUpdate, + phase0, + Root, + RootHex, + Slot, + ssz, + SSZTypesFor, + SyncPeriod, +} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import { CachedBeaconStateAltair, @@ -16,7 +32,14 @@ import { } from "@lodestar/light-client/spec"; import {Logger, MapDef, pruneSetToMax} from "@lodestar/utils"; import {routes} from "@lodestar/api"; -import {MIN_SYNC_COMMITTEE_PARTICIPANTS, SYNC_COMMITTEE_SIZE, ForkName, ForkSeq, ForkExecution} from "@lodestar/params"; +import { + MIN_SYNC_COMMITTEE_PARTICIPANTS, + SYNC_COMMITTEE_SIZE, + ForkName, + ForkSeq, + ForkExecution, + ForkLightClient, +} from "@lodestar/params"; import {IBeaconDb} from "../../db/index.js"; import {Metrics} from "../../metrics/index.js"; @@ -40,7 +63,7 @@ type DependentRootHex = RootHex; type BlockRooHex = RootHex; export type SyncAttestedData = { - attestedHeader: allForks.LightClientHeader; + attestedHeader: LightClientHeader; /** Precomputed root to prevent re-hashing */ blockRoot: Uint8Array; } & ( @@ -178,11 +201,11 @@ export class LightClientServer { * Keep in memory since this data is very transient, not useful after a few slots */ private readonly prevHeadData = new Map(); - private checkpointHeaders = new Map(); - private latestHeadUpdate: allForks.LightClientOptimisticUpdate | null = null; + private checkpointHeaders = new Map(); + private latestHeadUpdate: LightClientOptimisticUpdate | null = null; private readonly zero: Pick; - private finalized: allForks.LightClientFinalityUpdate | null = null; + private finalized: LightClientFinalityUpdate | null = null; constructor( private readonly opts: LightClientServerOpts, @@ -225,7 +248,7 @@ export class LightClientServer { * - Use block's syncAggregate */ onImportBlockHead( - block: allForks.AllForksLightClient["BeaconBlock"], + block: BeaconBlock, postState: CachedBeaconStateAltair, parentBlockSlot: Slot ): void { @@ -260,7 +283,7 @@ export class LightClientServer { /** * API ROUTE to get `currentSyncCommittee` and `nextSyncCommittee` from a trusted state root */ - async getBootstrap(blockRoot: Uint8Array): Promise { + async getBootstrap(blockRoot: Uint8Array): Promise { const syncCommitteeWitness = await this.db.syncCommitteeWitness.get(blockRoot); if (!syncCommitteeWitness) { throw new LightClientServerError( @@ -305,7 +328,7 @@ export class LightClientServer { * - Has the most bits * - Signed header at the oldest slot */ - async getUpdate(period: number): Promise { + async getUpdate(period: number): Promise { // Signature data const update = await this.db.bestLightClientUpdate.get(period); if (!update) { @@ -336,11 +359,11 @@ export class LightClientServer { * API ROUTE to poll LightclientHeaderUpdate. * Clients should use the SSE type `light_client_optimistic_update` if available */ - getOptimisticUpdate(): allForks.LightClientOptimisticUpdate | null { + getOptimisticUpdate(): LightClientOptimisticUpdate | null { return this.latestHeadUpdate; } - getFinalityUpdate(): allForks.LightClientFinalityUpdate | null { + getFinalityUpdate(): LightClientFinalityUpdate | null { return this.finalized; } @@ -356,7 +379,7 @@ export class LightClientServer { } private async persistPostBlockImportData( - block: allForks.AllForksLightClient["BeaconBlock"], + block: BeaconBlock, postState: CachedBeaconStateAltair, parentBlockSlot: Slot ): Promise { @@ -476,7 +499,7 @@ export class LightClientServer { return; } - const headerUpdate: allForks.LightClientOptimisticUpdate = { + const headerUpdate: LightClientOptimisticUpdate = { attestedHeader, syncAggregate, signatureSlot, @@ -633,7 +656,7 @@ export class LightClientServer { finalityBranch, syncAggregate, signatureSlot, - } as allForks.LightClientUpdate; + } as LightClientUpdate; // attestedData and the block of syncAggregate may not be in same sync period // should not use attested data slot as sync period @@ -669,7 +692,7 @@ export class LightClientServer { /** * Get finalized header from db. Keeps a small in-memory cache to speed up most of the lookups */ - private async getFinalizedHeader(finalizedBlockRoot: Uint8Array): Promise { + private async getFinalizedHeader(finalizedBlockRoot: Uint8Array): Promise { const finalizedBlockRootHex = toHexString(finalizedBlockRoot); const cachedFinalizedHeader = this.checkpointHeaders.get(finalizedBlockRootHex); if (cachedFinalizedHeader) { @@ -697,28 +720,23 @@ export function sumBits(bits: BitArray): number { return bits.getTrueBitIndexes().length; } -export function blockToLightClientHeader( - fork: ForkName, - block: allForks.AllForksLightClient["BeaconBlock"] -): allForks.LightClientHeader { +export function blockToLightClientHeader(fork: ForkName, block: BeaconBlock): LightClientHeader { const blockSlot = block.slot; const beacon: phase0.BeaconBlockHeader = { slot: blockSlot, proposerIndex: block.proposerIndex, parentRoot: block.parentRoot, stateRoot: block.stateRoot, - bodyRoot: (ssz[fork].BeaconBlockBody as allForks.AllForksLightClientSSZTypes["BeaconBlockBody"]).hashTreeRoot( - block.body - ), + bodyRoot: (ssz[fork].BeaconBlockBody as SSZTypesFor).hashTreeRoot(block.body), }; if (ForkSeq[fork] >= ForkSeq.capella) { - const blockBody = block.body as allForks.AllForksExecution["BeaconBlockBody"]; + const blockBody = block.body as BeaconBlockBody; const execution = executionPayloadToPayloadHeader(ForkSeq[fork], blockBody.executionPayload); return { beacon, execution, executionBranch: getBlockBodyExecutionHeaderProof(fork as ForkExecution, blockBody), - } as allForks.LightClientHeader; + } as LightClientHeader; } else { return {beacon}; } diff --git a/packages/beacon-node/src/chain/lightClient/proofs.ts b/packages/beacon-node/src/chain/lightClient/proofs.ts index cf1735e706d6..87ad4544ec69 100644 --- a/packages/beacon-node/src/chain/lightClient/proofs.ts +++ b/packages/beacon-node/src/chain/lightClient/proofs.ts @@ -1,7 +1,7 @@ import {Tree} from "@chainsafe/persistent-merkle-tree"; import {BeaconStateAllForks} from "@lodestar/state-transition"; import {FINALIZED_ROOT_GINDEX, BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX, ForkExecution} from "@lodestar/params"; -import {allForks, ssz} from "@lodestar/types"; +import {BeaconBlockBody, SSZTypesFor, ssz} from "@lodestar/types"; import {SyncCommitteeWitness} from "./types.js"; @@ -47,8 +47,8 @@ export function getFinalizedRootProof(state: BeaconStateAllForks): Uint8Array[] export function getBlockBodyExecutionHeaderProof( fork: ForkExecution, - body: allForks.AllForksExecution["BeaconBlockBody"] + body: BeaconBlockBody ): Uint8Array[] { - const bodyView = (ssz[fork].BeaconBlockBody as allForks.AllForksExecutionSSZTypes["BeaconBlockBody"]).toView(body); + const bodyView = (ssz[fork].BeaconBlockBody as SSZTypesFor).toView(body); return new Tree(bodyView.node).getSingleProof(BigInt(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX)); } diff --git a/packages/beacon-node/src/chain/opPools/opPool.ts b/packages/beacon-node/src/chain/opPools/opPool.ts index 1fdee886ff1d..69c331f6fd39 100644 --- a/packages/beacon-node/src/chain/opPools/opPool.ts +++ b/packages/beacon-node/src/chain/opPools/opPool.ts @@ -15,7 +15,7 @@ import { MAX_ATTESTER_SLASHINGS, ForkSeq, } from "@lodestar/params"; -import {Epoch, phase0, capella, ssz, ValidatorIndex, allForks} from "@lodestar/types"; +import {Epoch, phase0, capella, ssz, ValidatorIndex, SignedBeaconBlock} from "@lodestar/types"; import {IBeaconDb} from "../../db/index.js"; import {SignedBLSToExecutionChangeVersioned} from "../../util/types.js"; import {BlockType} from "../interface.js"; @@ -304,7 +304,7 @@ export class OpPool { /** * Prune all types of transactions given the latest head state */ - pruneAll(headBlock: allForks.SignedBeaconBlock, headState: CachedBeaconStateAllForks): void { + pruneAll(headBlock: SignedBeaconBlock, headState: CachedBeaconStateAllForks): void { this.pruneAttesterSlashings(headState); this.pruneProposerSlashings(headState); this.pruneVoluntaryExits(headState); @@ -377,10 +377,7 @@ export class OpPool { * In the worse case where head block is reorged, the same BlsToExecutionChange message can be re-added * to opPool once gossipsub seen cache TTL passes. */ - private pruneBlsToExecutionChanges( - headBlock: allForks.SignedBeaconBlock, - headState: CachedBeaconStateAllForks - ): void { + private pruneBlsToExecutionChanges(headBlock: SignedBeaconBlock, headState: CachedBeaconStateAllForks): void { const {config} = headState; const recentBlsToExecutionChanges = config.getForkSeq(headBlock.message.slot) >= ForkSeq.capella diff --git a/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts b/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts index ccc0595d0db6..bfa30e570e06 100644 --- a/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts +++ b/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts @@ -5,7 +5,7 @@ import { StateHashTreeRootSource, stateTransition, } from "@lodestar/state-transition"; -import {allForks, Gwei, Root} from "@lodestar/types"; +import {BeaconBlock, BlindedBeaconBlock, Gwei, Root} from "@lodestar/types"; import {ZERO_HASH} from "../../constants/index.js"; import {Metrics} from "../../metrics/index.js"; @@ -17,10 +17,10 @@ import {Metrics} from "../../metrics/index.js"; export function computeNewStateRoot( metrics: Metrics | null, state: CachedBeaconStateAllForks, - block: allForks.FullOrBlindedBeaconBlock + block: BeaconBlock | BlindedBeaconBlock ): {newStateRoot: Root; proposerReward: Gwei} { // Set signature to zero to re-use stateTransition() function which requires the SignedBeaconBlock type - const blockEmptySig = {message: block, signature: ZERO_HASH} as allForks.FullOrBlindedSignedBeaconBlock; + const blockEmptySig = {message: block, signature: ZERO_HASH}; const postState = stateTransition( state, diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index c1c1d5eaa447..5165b6b3f7ff 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -1,6 +1,5 @@ import { Bytes32, - allForks, Root, RootHex, Slot, @@ -11,6 +10,12 @@ import { capella, deneb, Wei, + SSEPayloadAttributes, + BeaconBlock, + BeaconBlockBody, + ExecutionPayloadHeader, + BlindedBeaconBlockBody, + BlindedBeaconBlock, } from "@lodestar/types"; import { CachedBeaconStateAllForks, @@ -27,7 +32,6 @@ import { import {ChainForkConfig} from "@lodestar/config"; import {ForkSeq, ForkExecution, isForkExecution} from "@lodestar/params"; import {toHex, sleep, Logger} from "@lodestar/utils"; - import type {BeaconChain} from "../chain.js"; import {PayloadId, IExecutionEngine, IExecutionBuilder, PayloadAttributes} from "../../execution/index.js"; import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js"; @@ -73,11 +77,9 @@ export enum BlockType { Blinded = "Blinded", } export type AssembledBodyType = T extends BlockType.Full - ? allForks.BeaconBlockBody - : allForks.BlindedBeaconBlockBody; -export type AssembledBlockType = T extends BlockType.Full - ? allForks.BeaconBlock - : allForks.BlindedBeaconBlock; + ? BeaconBlockBody + : BlindedBeaconBlockBody; +export type AssembledBlockType = T extends BlockType.Full ? BeaconBlock : BlindedBeaconBlock; export enum BlobsResultType { preDeneb, @@ -191,7 +193,7 @@ export async function produceBlockBody( currentState as CachedBeaconStateBellatrix, proposerPubKey ); - (blockBody as allForks.BlindedBeaconBlockBody).executionPayloadHeader = builderRes.header; + (blockBody as BlindedBeaconBlockBody).executionPayloadHeader = builderRes.header; executionPayloadValue = builderRes.executionPayloadValue; const fetchedTime = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.genesisTime); @@ -237,7 +239,7 @@ export async function produceBlockBody( ); if (prepareRes.isPremerge) { - (blockBody as allForks.ExecutionBlockBody).executionPayload = + (blockBody as BeaconBlockBody).executionPayload = ssz.allForksExecution[fork].ExecutionPayload.defaultValue(); blobsResult = {type: BlobsResultType.preDeneb}; executionPayloadValue = BigInt(0); @@ -258,7 +260,7 @@ export async function produceBlockBody( const {executionPayload, blobsBundle} = engineRes; shouldOverrideBuilder = engineRes.shouldOverrideBuilder; - (blockBody as allForks.ExecutionBlockBody).executionPayload = executionPayload; + (blockBody as BeaconBlockBody).executionPayload = executionPayload; executionPayloadValue = engineRes.executionPayloadValue; Object.assign(logMeta, {transactions: executionPayload.transactions.length, shouldOverrideBuilder}); @@ -307,7 +309,7 @@ export async function produceBlockBody( {}, e as Error ); - (blockBody as allForks.ExecutionBlockBody).executionPayload = + (blockBody as BeaconBlockBody).executionPayload = ssz.allForksExecution[fork].ExecutionPayload.defaultValue(); blobsResult = {type: BlobsResultType.preDeneb}; executionPayloadValue = BigInt(0); @@ -441,7 +443,7 @@ async function prepareExecutionPayloadHeader( state: CachedBeaconStateBellatrix, proposerPubKey: BLSPubkey ): Promise<{ - header: allForks.ExecutionPayloadHeader; + header: ExecutionPayloadHeader; executionPayloadValue: Wei; blobKzgCommitments?: deneb.BlobKzgCommitments; }> { @@ -504,7 +506,7 @@ export async function getPayloadAttributesForSSE( parentBlockRoot, feeRecipient, }: {prepareState: CachedBeaconStateExecutions; prepareSlot: Slot; parentBlockRoot: Root; feeRecipient: string} -): Promise { +): Promise { const parentHashRes = await getExecutionPayloadParentHash(chain, prepareState); if (!parentHashRes.isPremerge) { @@ -516,7 +518,7 @@ export async function getPayloadAttributesForSSE( feeRecipient, }); - const ssePayloadAttributes: allForks.SSEPayloadAttributes = { + const ssePayloadAttributes: SSEPayloadAttributes = { proposerIndex: prepareState.epochCtx.getBeaconProposer(prepareSlot), proposalSlot: prepareSlot, parentBlockNumber: prepareState.latestExecutionPayloadHeader.blockNumber, @@ -546,7 +548,7 @@ function preparePayloadAttributes( parentBlockRoot: Root; feeRecipient: string; } -): allForks.SSEPayloadAttributes["payloadAttributes"] { +): SSEPayloadAttributes["payloadAttributes"] { const timestamp = computeTimeAtSlot(chain.config, prepareSlot, prepareState.genesisTime); const prevRandao = getRandaoMix(prepareState, prepareState.epochCtx.epoch); const payloadAttributes = { diff --git a/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts index 54e90672d189..ba086ecafc7e 100644 --- a/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts +++ b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts @@ -1,11 +1,11 @@ -import {allForks} from "@lodestar/types"; +import {ExecutionPayload} from "@lodestar/types"; import {BlobsBundle} from "../../execution/index.js"; /** * Optionally sanity-check that the KZG commitments match the versioned hashes in the transactions * https://github.com/ethereum/consensus-specs/blob/11a037fd9227e29ee809c9397b09f8cc3383a8c0/specs/eip4844/validator.md#blob-kzg-commitments */ -export function validateBlobsAndKzgCommitments(payload: allForks.ExecutionPayload, blobsBundle: BlobsBundle): void { +export function validateBlobsAndKzgCommitments(payload: ExecutionPayload, blobsBundle: BlobsBundle): void { // sanity-check that the KZG commitments match the blobs (as produced by the execution engine) if (blobsBundle.blobs.length !== blobsBundle.commitments.length) { throw Error( diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index a1021de4aeab..341625c9ff1d 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -1,4 +1,4 @@ -import {allForks, phase0, Slot, RootHex, Epoch} from "@lodestar/types"; +import {phase0, Slot, RootHex, Epoch, BeaconBlock} from "@lodestar/types"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {routes} from "@lodestar/api"; import {ProtoBlock} from "@lodestar/fork-choice"; @@ -36,7 +36,7 @@ export interface IStateRegenerator extends IStateRegeneratorInternal { dropCache(): void; dumpCacheSummary(): routes.lodestar.StateCacheItem[]; getStateSync(stateRoot: RootHex): CachedBeaconStateAllForks | null; - getPreStateSync(block: allForks.BeaconBlock): CachedBeaconStateAllForks | null; + getPreStateSync(block: BeaconBlock): CachedBeaconStateAllForks | null; getCheckpointStateOrBytes(cp: CheckpointHex): Promise; getCheckpointStateSync(cp: CheckpointHex): CachedBeaconStateAllForks | null; getClosestHeadState(head: ProtoBlock): CachedBeaconStateAllForks | null; @@ -56,11 +56,7 @@ export interface IStateRegeneratorInternal { * Return a valid pre-state for a beacon block * This will always return a state in the latest viable epoch */ - getPreState( - block: allForks.BeaconBlock, - opts: StateCloneOpts, - rCaller: RegenCaller - ): Promise; + getPreState(block: BeaconBlock, opts: StateCloneOpts, rCaller: RegenCaller): Promise; /** * Return a valid checkpoint state diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index 148ec756497d..358a37e6e638 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -1,5 +1,5 @@ import {toHexString} from "@chainsafe/ssz"; -import {phase0, Slot, allForks, RootHex, Epoch} from "@lodestar/types"; +import {phase0, Slot, RootHex, Epoch, BeaconBlock} from "@lodestar/types"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition"; import {Logger} from "@lodestar/utils"; @@ -86,7 +86,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { * which is usually the gossip block. */ getPreStateSync( - block: allForks.BeaconBlock, + block: BeaconBlock, opts: StateCloneOpts = {dontTransferCache: true} ): CachedBeaconStateAllForks | null { const parentRoot = toHexString(block.parentRoot); @@ -200,7 +200,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { * - State after `block.parentRoot` dialed forward to block.slot */ async getPreState( - block: allForks.BeaconBlock, + block: BeaconBlock, opts: StateCloneOpts, rCaller: RegenCaller ): Promise { diff --git a/packages/beacon-node/src/chain/regen/regen.ts b/packages/beacon-node/src/chain/regen/regen.ts index 3fe6f08e1383..2b6fc835cf7c 100644 --- a/packages/beacon-node/src/chain/regen/regen.ts +++ b/packages/beacon-node/src/chain/regen/regen.ts @@ -1,5 +1,5 @@ import {fromHexString, toHexString} from "@chainsafe/ssz"; -import {allForks, phase0, Slot, RootHex} from "@lodestar/types"; +import {phase0, Slot, RootHex, BeaconBlock} from "@lodestar/types"; import { CachedBeaconStateAllForks, computeEpochAtSlot, @@ -50,7 +50,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { * - reload state if needed in this flow */ async getPreState( - block: allForks.BeaconBlock, + block: BeaconBlock, opts: StateCloneOpts, regenCaller: RegenCaller ): Promise { diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index bd8bf3537582..65dc23496070 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -5,7 +5,7 @@ import { getAttesterSlashableIndices, processAttestationsAltair, } from "@lodestar/state-transition"; -import {allForks, altair, phase0} from "@lodestar/types"; +import {BeaconBlock, altair, phase0} from "@lodestar/types"; import {ForkName, WHISTLEBLOWER_REWARD_QUOTIENT} from "@lodestar/params"; import {routes} from "@lodestar/api"; @@ -21,7 +21,7 @@ type SubRewardValue = number; // All reward values should be integer * 3) Reporting slashable behaviours from proposer and attester */ export async function computeBlockRewards( - block: allForks.BeaconBlock, + block: BeaconBlock, preState: CachedBeaconStateAllForks, postState?: CachedBeaconStateAllForks ): Promise { @@ -99,10 +99,7 @@ function computeSyncAggregateReward(block: altair.BeaconBlock, preState: CachedB * Calculate rewards received by block proposer for including proposer slashings. * All proposer slashing rewards go to block proposer and none to whistleblower as of Deneb */ -function computeBlockProposerSlashingReward( - block: allForks.BeaconBlock, - state: CachedBeaconStateAllForks -): SubRewardValue { +function computeBlockProposerSlashingReward(block: BeaconBlock, state: CachedBeaconStateAllForks): SubRewardValue { let proposerSlashingReward = 0; for (const proposerSlashing of block.body.proposerSlashings) { @@ -119,10 +116,7 @@ function computeBlockProposerSlashingReward( * Calculate rewards received by block proposer for including attester slashings. * All attester slashing rewards go to block proposer and none to whistleblower as of Deneb */ -function computeBlockAttesterSlashingReward( - block: allForks.BeaconBlock, - preState: CachedBeaconStateAllForks -): SubRewardValue { +function computeBlockAttesterSlashingReward(block: BeaconBlock, preState: CachedBeaconStateAllForks): SubRewardValue { let attesterSlashingReward = 0; for (const attesterSlashing of block.body.attesterSlashings) { diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index 7922e20c2317..89ef84af43a2 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -1,5 +1,5 @@ import {CachedBeaconStateAllForks, CachedBeaconStateAltair} from "@lodestar/state-transition"; -import {ValidatorIndex, allForks, altair} from "@lodestar/types"; +import {BeaconBlock, ValidatorIndex, altair} from "@lodestar/types"; import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; import {routes} from "@lodestar/api"; @@ -7,7 +7,7 @@ export type SyncCommitteeRewards = routes.beacon.SyncCommitteeRewards; type BalanceRecord = {val: number}; // Use val for convenient way to increment/decrement balance export async function computeSyncCommitteeRewards( - block: allForks.BeaconBlock, + block: BeaconBlock, preState: CachedBeaconStateAllForks, validatorIds: (ValidatorIndex | string)[] = [] ): Promise { diff --git a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts index 7e8d8a7ebcbc..6b51332353f2 100644 --- a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts +++ b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts @@ -1,5 +1,5 @@ import {toHexString} from "@chainsafe/ssz"; -import {deneb, RootHex, ssz, allForks} from "@lodestar/types"; +import {deneb, RootHex, SignedBeaconBlock, ssz} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {pruneSetToMax} from "@lodestar/utils"; import {BLOBSIDECAR_FIXED_SIZE, isForkBlobs, ForkName} from "@lodestar/params"; @@ -23,12 +23,12 @@ export enum BlockInputAvailabilitySource { } type GossipedBlockInput = - | {type: GossipedInputType.block; signedBlock: allForks.SignedBeaconBlock; blockBytes: Uint8Array | null} + | {type: GossipedInputType.block; signedBlock: SignedBeaconBlock; blockBytes: Uint8Array | null} | {type: GossipedInputType.blob; blobSidecar: deneb.BlobSidecar; blobBytes: Uint8Array | null}; type BlockInputCacheType = { fork: ForkName; - block?: allForks.SignedBeaconBlock; + block?: SignedBeaconBlock; blockBytes?: Uint8Array | null; cachedData?: CachedData; // block promise and its callback cached for delayed resolution diff --git a/packages/beacon-node/src/chain/validation/block.ts b/packages/beacon-node/src/chain/validation/block.ts index 1d12110ad1c3..214eeaf0ab4e 100644 --- a/packages/beacon-node/src/chain/validation/block.ts +++ b/packages/beacon-node/src/chain/validation/block.ts @@ -1,6 +1,5 @@ import {toHexString} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {allForks} from "@lodestar/types"; import { computeStartSlotAtEpoch, computeTimeAtSlot, @@ -11,6 +10,7 @@ import { } from "@lodestar/state-transition"; import {sleep} from "@lodestar/utils"; import {ForkName} from "@lodestar/params"; +import {SignedBeaconBlock} from "@lodestar/types"; import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js"; import {IBeaconChain} from "../interface.js"; import {BlockGossipError, BlockErrorCode, GossipAction} from "../errors/index.js"; @@ -19,7 +19,7 @@ import {RegenCaller} from "../regen/index.js"; export async function validateGossipBlock( config: ChainForkConfig, chain: IBeaconChain, - signedBlock: allForks.SignedBeaconBlock, + signedBlock: SignedBeaconBlock, fork: ForkName ): Promise { const block = signedBlock.message; diff --git a/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts b/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts index 1837d8d8cd62..23b91dba5fd9 100644 --- a/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts +++ b/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts @@ -1,5 +1,5 @@ import {ChainForkConfig} from "@lodestar/config"; -import {allForks} from "@lodestar/types"; +import {LightClientFinalityUpdate} from "@lodestar/types"; import {IBeaconChain} from "../interface.js"; import {LightClientError, LightClientErrorCode} from "../errors/lightClientError.js"; import {GossipAction} from "../errors/index.js"; @@ -9,7 +9,7 @@ import {updateReceivedTooEarly} from "./lightClientOptimisticUpdate.js"; export function validateLightClientFinalityUpdate( config: ChainForkConfig, chain: IBeaconChain, - gossipedFinalityUpdate: allForks.LightClientFinalityUpdate + gossipedFinalityUpdate: LightClientFinalityUpdate ): void { // [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; diff --git a/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts b/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts index 8077378f4758..54b69f56808c 100644 --- a/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts +++ b/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts @@ -1,6 +1,6 @@ -import {allForks} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {computeTimeAtSlot} from "@lodestar/state-transition"; +import {LightClientOptimisticUpdate} from "@lodestar/types"; import {IBeaconChain} from "../interface.js"; import {LightClientError, LightClientErrorCode} from "../errors/lightClientError.js"; import {GossipAction} from "../errors/index.js"; @@ -10,7 +10,7 @@ import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js"; export function validateLightClientOptimisticUpdate( config: ChainForkConfig, chain: IBeaconChain, - gossipedOptimisticUpdate: allForks.LightClientOptimisticUpdate + gossipedOptimisticUpdate: LightClientOptimisticUpdate ): void { // [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; @@ -56,7 +56,7 @@ export function validateLightClientOptimisticUpdate( export function updateReceivedTooEarly( config: ChainForkConfig, genesisTime: number, - update: Pick + update: Pick ): boolean { const signatureSlot13TimestampMs = computeTimeAtSlot(config, update.signatureSlot + 1 / 3, genesisTime) * 1000; const earliestAllowedTimestampMs = signatureSlot13TimestampMs - MAXIMUM_GOSSIP_CLOCK_DISPARITY; diff --git a/packages/beacon-node/src/db/buckets.ts b/packages/beacon-node/src/db/buckets.ts index 9dffd0608d52..eff123879037 100644 --- a/packages/beacon-node/src/db/buckets.ts +++ b/packages/beacon-node/src/db/buckets.ts @@ -29,7 +29,7 @@ export enum Bucket { phase0_attesterSlashing = 15, // Root -> AttesterSlashing capella_blsToExecutionChange = 16, // ValidatorIndex -> SignedBLSToExecutionChange // checkpoint states - allForks_checkpointState = 17, // Root -> allForks.BeaconState + allForks_checkpointState = 17, // Root -> BeaconState // allForks_pendingBlock = 25, // Root -> SignedBeaconBlock // DEPRECATED on v0.30.0 phase0_depositEvent = 19, // depositIndex -> DepositEvent diff --git a/packages/beacon-node/src/db/repositories/block.ts b/packages/beacon-node/src/db/repositories/block.ts index 14f4490087da..b01acb8c2ea8 100644 --- a/packages/beacon-node/src/db/repositories/block.ts +++ b/packages/beacon-node/src/db/repositories/block.ts @@ -1,6 +1,6 @@ import {ChainForkConfig} from "@lodestar/config"; import {Db, Repository} from "@lodestar/db"; -import {allForks, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, ssz} from "@lodestar/types"; import {getSignedBlockTypeFromBytes} from "../../util/multifork.js"; import {Bucket, getBucketNameByValue} from "../buckets.js"; @@ -9,7 +9,7 @@ import {Bucket, getBucketNameByValue} from "../buckets.js"; * * Used to store unfinalized blocks */ -export class BlockRepository extends Repository { +export class BlockRepository extends Repository { constructor(config: ChainForkConfig, db: Db) { const bucket = Bucket.allForks_block; const type = ssz.phase0.SignedBeaconBlock; // Pick some type but won't be used @@ -19,15 +19,15 @@ export class BlockRepository extends Repository & { /** * Stores finalized blocks. Block slot is identifier. */ -export class BlockArchiveRepository extends Repository { +export class BlockArchiveRepository extends Repository { constructor(config: ChainForkConfig, db: Db) { const bucket = Bucket.allForks_blockArchive; const type = ssz.phase0.SignedBeaconBlock; // Pick some type but won't be used @@ -30,17 +30,17 @@ export class BlockArchiveRepository extends Repository { + async put(key: Slot, value: SignedBeaconBlock): Promise { const blockRoot = this.config.getForkTypes(value.message.slot).BeaconBlock.hashTreeRoot(value.message); const slot = value.message.slot; await Promise.all([ @@ -60,7 +60,7 @@ export class BlockArchiveRepository extends Repository[]): Promise { + async batchPut(items: KeyValue[]): Promise { await Promise.all([ super.batchPut(items), Array.from(items).map((item) => { @@ -84,7 +84,7 @@ export class BlockArchiveRepository extends Repository { + async remove(value: SignedBeaconBlock): Promise { await Promise.all([ super.remove(value), deleteRootIndex(this.db, this.config.getForkTypes(value.message.slot).SignedBeaconBlock, value), @@ -92,7 +92,7 @@ export class BlockArchiveRepository extends Repository { + async batchRemove(values: SignedBeaconBlock[]): Promise { await Promise.all([ super.batchRemove(values), Array.from(values).map((value) => @@ -102,7 +102,7 @@ export class BlockArchiveRepository extends Repository { + async *valuesStream(opts?: BlockFilterOptions): AsyncIterable { const firstSlot = this.getFirstSlot(opts); const valuesStream = super.valuesStream(opts); const step = (opts && opts.step) ?? 1; @@ -114,13 +114,13 @@ export class BlockArchiveRepository extends Repository { + async values(opts?: BlockFilterOptions): Promise { return all(this.valuesStream(opts)); } // INDEX - async getByRoot(root: Root): Promise { + async getByRoot(root: Root): Promise { const slot = await this.getSlotByRoot(root); return slot !== null ? this.get(slot) : null; } @@ -130,7 +130,7 @@ export class BlockArchiveRepository extends Repository) : null; } - async getByParentRoot(root: Root): Promise { + async getByParentRoot(root: Root): Promise { const slot = await this.getSlotByParentRoot(root); return slot !== null ? this.get(slot) : null; } diff --git a/packages/beacon-node/src/db/repositories/blockArchiveIndex.ts b/packages/beacon-node/src/db/repositories/blockArchiveIndex.ts index 53d08d3a2713..797142d09db7 100644 --- a/packages/beacon-node/src/db/repositories/blockArchiveIndex.ts +++ b/packages/beacon-node/src/db/repositories/blockArchiveIndex.ts @@ -1,6 +1,7 @@ import {Db, encodeKey} from "@lodestar/db"; -import {Slot, Root, allForks, ssz} from "@lodestar/types"; +import {Slot, Root, ssz, SignedBeaconBlock, SSZTypesFor} from "@lodestar/types"; import {intToBytes} from "@lodestar/utils"; +import {ForkAll} from "@lodestar/params"; import {Bucket} from "../buckets.js"; export async function storeRootIndex(db: Db, slot: Slot, blockRoot: Root): Promise { @@ -13,14 +14,14 @@ export async function storeParentRootIndex(db: Db, slot: Slot, parentRoot: Root) export async function deleteRootIndex( db: Db, - signedBeaconBlockType: allForks.AllForksSSZTypes["SignedBeaconBlock"], - block: allForks.SignedBeaconBlock + signedBeaconBlockType: SSZTypesFor, + block: SignedBeaconBlock ): Promise { const beaconBlockType = (signedBeaconBlockType as typeof ssz.phase0.SignedBeaconBlock).fields["message"]; return db.delete(getRootIndexKey(beaconBlockType.hashTreeRoot(block.message))); } -export async function deleteParentRootIndex(db: Db, block: allForks.SignedBeaconBlock): Promise { +export async function deleteParentRootIndex(db: Db, block: SignedBeaconBlock): Promise { return db.delete(getParentRootIndexKey(block.message.parentRoot)); } diff --git a/packages/beacon-node/src/db/repositories/lightclientBestUpdate.ts b/packages/beacon-node/src/db/repositories/lightclientBestUpdate.ts index 24e0883d1be6..26493e35e408 100644 --- a/packages/beacon-node/src/db/repositories/lightclientBestUpdate.ts +++ b/packages/beacon-node/src/db/repositories/lightclientBestUpdate.ts @@ -1,6 +1,6 @@ import {ChainForkConfig} from "@lodestar/config"; import {DatabaseController, Repository} from "@lodestar/db"; -import {ssz, SyncPeriod, allForks} from "@lodestar/types"; +import {LightClientUpdate, ssz, SyncPeriod} from "@lodestar/types"; import {Bucket, getBucketNameByValue} from "../buckets.js"; const SLOT_BYTE_COUNT = 8; @@ -10,7 +10,7 @@ const SLOT_BYTE_COUNT = 8; * * Used to prepare light client updates */ -export class BestLightClientUpdateRepository extends Repository { +export class BestLightClientUpdateRepository extends Repository { constructor(config: ChainForkConfig, db: DatabaseController) { // Pick some type but won't be used const bucket = Bucket.lightClient_bestLightClientUpdate; @@ -18,7 +18,7 @@ export class BestLightClientUpdateRepository extends Repository { +export class CheckpointHeaderRepository extends Repository { constructor(config: ChainForkConfig, db: DatabaseController) { // Pick some type but won't be used const bucket = Bucket.lightClient_checkpointHeader; @@ -18,11 +18,11 @@ export class CheckpointHeaderRepository extends Repository; constructor(config: ChainForkConfig, db: Db) { this.config = config; diff --git a/packages/beacon-node/src/execution/builder/http.ts b/packages/beacon-node/src/execution/builder/http.ts index 7637023a9c08..9791f93a8ee2 100644 --- a/packages/beacon-node/src/execution/builder/http.ts +++ b/packages/beacon-node/src/execution/builder/http.ts @@ -1,4 +1,14 @@ -import {allForks, bellatrix, Slot, Root, BLSPubkey, deneb, Wei} from "@lodestar/types"; +import { + bellatrix, + Slot, + Root, + BLSPubkey, + deneb, + Wei, + SignedBeaconBlockOrContents, + SignedBlindedBeaconBlock, + ExecutionPayloadHeader, +} from "@lodestar/types"; import {parseExecutionPayloadAndBlobsBundle, reconstructFullBlockOrContents} from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; import {Logger} from "@lodestar/logger"; @@ -101,7 +111,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { parentHash: Root, proposerPubkey: BLSPubkey ): Promise<{ - header: allForks.ExecutionPayloadHeader; + header: ExecutionPayloadHeader; executionPayloadValue: Wei; blobKzgCommitments?: deneb.BlobKzgCommitments; }> { @@ -116,9 +126,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { return {header, executionPayloadValue, blobKzgCommitments}; } - async submitBlindedBlock( - signedBlindedBlock: allForks.SignedBlindedBeaconBlock - ): Promise { + async submitBlindedBlock(signedBlindedBlock: SignedBlindedBeaconBlock): Promise { const data = (await this.api.submitBlindedBlock({signedBlindedBlock}, {retries: 2})).value(); const {executionPayload, blobsBundle} = parseExecutionPayloadAndBlobsBundle(data); diff --git a/packages/beacon-node/src/execution/builder/interface.ts b/packages/beacon-node/src/execution/builder/interface.ts index 8754a3616610..9a655a68de02 100644 --- a/packages/beacon-node/src/execution/builder/interface.ts +++ b/packages/beacon-node/src/execution/builder/interface.ts @@ -1,4 +1,14 @@ -import {allForks, bellatrix, Root, Slot, BLSPubkey, deneb, Wei} from "@lodestar/types"; +import { + bellatrix, + Root, + Slot, + BLSPubkey, + deneb, + Wei, + SignedBeaconBlockOrContents, + ExecutionPayloadHeader, + SignedBlindedBeaconBlock, +} from "@lodestar/types"; import {ForkExecution} from "@lodestar/params"; export interface IExecutionBuilder { @@ -23,9 +33,9 @@ export interface IExecutionBuilder { parentHash: Root, proposerPubKey: BLSPubkey ): Promise<{ - header: allForks.ExecutionPayloadHeader; + header: ExecutionPayloadHeader; executionPayloadValue: Wei; blobKzgCommitments?: deneb.BlobKzgCommitments; }>; - submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlock): Promise; + submitBlindedBlock(signedBlock: SignedBlindedBeaconBlock): Promise; } diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index f5ec03f41626..0af956c87668 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -1,4 +1,4 @@ -import {Root, RootHex, allForks, Wei} from "@lodestar/types"; +import {ExecutionPayload, Root, RootHex, Wei} from "@lodestar/types"; import {SLOTS_PER_EPOCH, ForkName, ForkSeq} from "@lodestar/params"; import {Logger} from "@lodestar/logger"; import { @@ -171,7 +171,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { */ async notifyNewPayload( fork: ForkName, - executionPayload: allForks.ExecutionPayload, + executionPayload: ExecutionPayload, versionedHashes?: VersionedHashes, parentBlockRoot?: Root ): Promise { @@ -364,7 +364,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { fork: ForkName, payloadId: PayloadId ): Promise<{ - executionPayload: allForks.ExecutionPayload; + executionPayload: ExecutionPayload; executionPayloadValue: Wei; blobsBundle?: BlobsBundle; shouldOverrideBuilder?: boolean; diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index e5f612fc0965..b8f319700738 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -1,6 +1,6 @@ import {ForkName} from "@lodestar/params"; import {KZGCommitment, Blob, KZGProof} from "@lodestar/types/deneb"; -import {Root, RootHex, allForks, capella, Wei} from "@lodestar/types"; +import {Root, RootHex, capella, Wei, ExecutionPayload} from "@lodestar/types"; import {DATA} from "../../eth1/provider/utils.js"; import {PayloadIdCache, PayloadId, WithdrawalV1} from "./payloadIdCache.js"; @@ -101,7 +101,7 @@ export interface IExecutionEngine { */ notifyNewPayload( fork: ForkName, - executionPayload: allForks.ExecutionPayload, + executionPayload: ExecutionPayload, versionedHashes?: VersionedHashes, parentBeaconBlockRoot?: Root ): Promise; @@ -137,7 +137,7 @@ export interface IExecutionEngine { fork: ForkName, payloadId: PayloadId ): Promise<{ - executionPayload: allForks.ExecutionPayload; + executionPayload: ExecutionPayload; executionPayloadValue: Wei; blobsBundle?: BlobsBundle; shouldOverrideBuilder?: boolean; diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index 72a0100f7a51..9fe9a990f76d 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -1,4 +1,4 @@ -import {allForks, capella, deneb, Wei, bellatrix, Root} from "@lodestar/types"; +import {capella, deneb, Wei, bellatrix, Root, ExecutionPayload} from "@lodestar/types"; import { BYTES_PER_LOGS_BLOOM, FIELD_ELEMENTS_PER_BLOB, @@ -163,7 +163,7 @@ export interface BlobsBundleRpc { proofs: DATA[]; // some ELs could also provide proofs, each 48 bytes } -export function serializeExecutionPayload(fork: ForkName, data: allForks.ExecutionPayload): ExecutionPayloadRpc { +export function serializeExecutionPayload(fork: ForkName, data: ExecutionPayload): ExecutionPayloadRpc { const payload: ExecutionPayloadRpc = { parentHash: bytesToData(data.parentHash), feeRecipient: bytesToData(data.feeRecipient), @@ -209,7 +209,7 @@ export function parseExecutionPayload( fork: ForkName, response: ExecutionPayloadResponse ): { - executionPayload: allForks.ExecutionPayload; + executionPayload: ExecutionPayload; executionPayloadValue: Wei; blobsBundle?: BlobsBundle; shouldOverrideBuilder?: boolean; diff --git a/packages/beacon-node/src/metrics/metrics/lodestar.ts b/packages/beacon-node/src/metrics/metrics/lodestar.ts index 12b7f3538d6e..5ebdbac48959 100644 --- a/packages/beacon-node/src/metrics/metrics/lodestar.ts +++ b/packages/beacon-node/src/metrics/metrics/lodestar.ts @@ -1,5 +1,5 @@ import {EpochTransitionStep, StateCloneSource, StateHashTreeRootSource} from "@lodestar/state-transition"; -import {allForks} from "@lodestar/types"; +import {BeaconState} from "@lodestar/types"; import {BlockSource, BlobsSource} from "../../chain/blocks/types.js"; import {JobQueueItemType} from "../../chain/bls/index.js"; import {BlockErrorCode} from "../../chain/errors/index.js"; @@ -28,7 +28,7 @@ export type LodestarMetrics = ReturnType; export function createLodestarMetrics( register: RegistryMetricCreator, metadata?: LodestarMetadata, - anchorState?: Pick + anchorState?: Pick ) { if (metadata) { register.static({ diff --git a/packages/beacon-node/src/metrics/validatorMonitor.ts b/packages/beacon-node/src/metrics/validatorMonitor.ts index 1104b1198fae..a9d783786e88 100644 --- a/packages/beacon-node/src/metrics/validatorMonitor.ts +++ b/packages/beacon-node/src/metrics/validatorMonitor.ts @@ -10,7 +10,7 @@ import { ParticipationFlags, } from "@lodestar/state-transition"; import {LogData, LogHandler, LogLevel, Logger, MapDef, MapDefMax, toHex} from "@lodestar/utils"; -import {RootHex, allForks, altair, deneb} from "@lodestar/types"; +import {BeaconBlock, RootHex, altair, deneb} from "@lodestar/types"; import {ChainConfig, ChainForkConfig} from "@lodestar/config"; import {ForkSeq, INTERVALS_PER_SLOT, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params"; import {Epoch, Slot, ValidatorIndex} from "@lodestar/types"; @@ -40,9 +40,9 @@ export type ValidatorMonitor = { registerLocalValidator(index: number): void; registerLocalValidatorInSyncCommittee(index: number, untilEpoch: Epoch): void; registerValidatorStatuses(currentEpoch: Epoch, statuses: AttesterStatus[], balances?: number[]): void; - registerBeaconBlock(src: OpSource, seenTimestampSec: Seconds, block: allForks.BeaconBlock): void; + registerBeaconBlock(src: OpSource, seenTimestampSec: Seconds, block: BeaconBlock): void; registerBlobSidecar(src: OpSource, seenTimestampSec: Seconds, blob: deneb.BlobSidecar): void; - registerImportedBlock(block: allForks.BeaconBlock, data: {proposerBalanceDelta: number}): void; + registerImportedBlock(block: BeaconBlock, data: {proposerBalanceDelta: number}): void; onPoolSubmitUnaggregatedAttestation( seenTimestampSec: number, indexedAttestation: IndexedAttestation, diff --git a/packages/beacon-node/src/network/gossip/interface.ts b/packages/beacon-node/src/network/gossip/interface.ts index df26c2328c70..25a871b4e2a0 100644 --- a/packages/beacon-node/src/network/gossip/interface.ts +++ b/packages/beacon-node/src/network/gossip/interface.ts @@ -2,7 +2,16 @@ import {Libp2p} from "libp2p"; import {Message, TopicValidatorResult} from "@libp2p/interface"; import {PeerIdStr} from "@chainsafe/libp2p-gossipsub/types"; import {ForkName} from "@lodestar/params"; -import {allForks, altair, capella, deneb, phase0, Slot} from "@lodestar/types"; +import { + altair, + capella, + deneb, + LightClientFinalityUpdate, + LightClientOptimisticUpdate, + phase0, + SignedBeaconBlock, + Slot, +} from "@lodestar/types"; import {BeaconConfig} from "@lodestar/config"; import {Logger} from "@lodestar/utils"; import {IBeaconChain} from "../../chain/index.js"; @@ -69,7 +78,7 @@ export type SSZTypeOfGossipTopic = T extends {type: infer : never; export type GossipTypeMap = { - [GossipType.beacon_block]: allForks.SignedBeaconBlock; + [GossipType.beacon_block]: SignedBeaconBlock; [GossipType.blob_sidecar]: deneb.BlobSidecar; [GossipType.beacon_aggregate_and_proof]: phase0.SignedAggregateAndProof; [GossipType.beacon_attestation]: phase0.Attestation; @@ -78,13 +87,13 @@ export type GossipTypeMap = { [GossipType.attester_slashing]: phase0.AttesterSlashing; [GossipType.sync_committee_contribution_and_proof]: altair.SignedContributionAndProof; [GossipType.sync_committee]: altair.SyncCommitteeMessage; - [GossipType.light_client_finality_update]: allForks.LightClientFinalityUpdate; - [GossipType.light_client_optimistic_update]: allForks.LightClientOptimisticUpdate; + [GossipType.light_client_finality_update]: LightClientFinalityUpdate; + [GossipType.light_client_optimistic_update]: LightClientOptimisticUpdate; [GossipType.bls_to_execution_change]: capella.SignedBLSToExecutionChange; }; export type GossipFnByType = { - [GossipType.beacon_block]: (signedBlock: allForks.SignedBeaconBlock) => Promise | void; + [GossipType.beacon_block]: (signedBlock: SignedBeaconBlock) => Promise | void; [GossipType.blob_sidecar]: (blobSidecar: deneb.BlobSidecar) => Promise | void; [GossipType.beacon_aggregate_and_proof]: (aggregateAndProof: phase0.SignedAggregateAndProof) => Promise | void; [GossipType.beacon_attestation]: (attestation: phase0.Attestation) => Promise | void; @@ -96,10 +105,10 @@ export type GossipFnByType = { ) => Promise | void; [GossipType.sync_committee]: (syncCommittee: altair.SyncCommitteeMessage) => Promise | void; [GossipType.light_client_finality_update]: ( - lightClientFinalityUpdate: allForks.LightClientFinalityUpdate + lightClientFinalityUpdate: LightClientFinalityUpdate ) => Promise | void; [GossipType.light_client_optimistic_update]: ( - lightClientOptimisticUpdate: allForks.LightClientOptimisticUpdate + lightClientOptimisticUpdate: LightClientOptimisticUpdate ) => Promise | void; [GossipType.bls_to_execution_change]: ( blsToExecutionChange: capella.SignedBLSToExecutionChange diff --git a/packages/beacon-node/src/network/interface.ts b/packages/beacon-node/src/network/interface.ts index aeeb61f1feb2..5012650e229a 100644 --- a/packages/beacon-node/src/network/interface.ts +++ b/packages/beacon-node/src/network/interface.ts @@ -16,7 +16,17 @@ import { import type {AddressManager, ConnectionManager, Registrar, TransportManager} from "@libp2p/interface-internal"; import type {Datastore} from "interface-datastore"; import {Identify} from "@chainsafe/libp2p-identify"; -import {Slot, SlotRootHex, allForks, altair, capella, deneb, phase0} from "@lodestar/types"; +import { + LightClientFinalityUpdate, + LightClientOptimisticUpdate, + SignedBeaconBlock, + Slot, + SlotRootHex, + altair, + capella, + deneb, + phase0, +} from "@lodestar/types"; import {PeerIdStr} from "../util/peerId.js"; import {INetworkEventBus} from "./events.js"; import {INetworkCorePublic} from "./core/types.js"; @@ -50,16 +60,16 @@ export interface INetwork extends INetworkCorePublic { sendBeaconBlocksByRange( peerId: PeerIdStr, request: phase0.BeaconBlocksByRangeRequest - ): Promise[]>; + ): Promise[]>; sendBeaconBlocksByRoot( peerId: PeerIdStr, request: phase0.BeaconBlocksByRootRequest - ): Promise[]>; + ): Promise[]>; sendBlobSidecarsByRange(peerId: PeerIdStr, request: deneb.BlobSidecarsByRangeRequest): Promise; sendBlobSidecarsByRoot(peerId: PeerIdStr, request: deneb.BlobSidecarsByRootRequest): Promise; // Gossip - publishBeaconBlock(signedBlock: allForks.SignedBeaconBlock): Promise; + publishBeaconBlock(signedBlock: SignedBeaconBlock): Promise; publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise; publishBeaconAggregateAndProof(aggregateAndProof: phase0.SignedAggregateAndProof): Promise; publishBeaconAttestation(attestation: phase0.Attestation, subnet: number): Promise; @@ -69,8 +79,8 @@ export interface INetwork extends INetworkCorePublic { publishAttesterSlashing(attesterSlashing: phase0.AttesterSlashing): Promise; publishSyncCommitteeSignature(signature: altair.SyncCommitteeMessage, subnet: number): Promise; publishContributionAndProof(contributionAndProof: altair.SignedContributionAndProof): Promise; - publishLightClientFinalityUpdate(update: allForks.LightClientFinalityUpdate): Promise; - publishLightClientOptimisticUpdate(update: allForks.LightClientOptimisticUpdate): Promise; + publishLightClientFinalityUpdate(update: LightClientFinalityUpdate): Promise; + publishLightClientOptimisticUpdate(update: LightClientOptimisticUpdate): Promise; // Debug dumpGossipQueue(gossipType: GossipType): Promise; diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index be8bb5114d40..52b9d85c0064 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -5,7 +5,19 @@ import {BeaconConfig} from "@lodestar/config"; import {sleep} from "@lodestar/utils"; import {LoggerNode} from "@lodestar/logger/node"; import {computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition"; -import {phase0, allForks, deneb, altair, Root, capella, SlotRootHex} from "@lodestar/types"; +import { + phase0, + deneb, + altair, + Root, + capella, + SlotRootHex, + SignedBeaconBlock, + LightClientBootstrap, + LightClientFinalityUpdate, + LightClientOptimisticUpdate, + LightClientUpdate, +} from "@lodestar/types"; import {routes} from "@lodestar/api"; import {ResponseIncoming} from "@lodestar/reqresp"; import {ForkSeq, MAX_BLOBS_PER_BLOCK} from "@lodestar/params"; @@ -287,7 +299,7 @@ export class Network implements INetwork { // Gossip - async publishBeaconBlock(signedBlock: allForks.SignedBeaconBlock): Promise { + async publishBeaconBlock(signedBlock: SignedBeaconBlock): Promise { const fork = this.config.getForkName(signedBlock.message.slot); return this.publishGossip({type: GossipType.beacon_block, fork}, signedBlock, { ignoreDuplicatePublishError: true, @@ -380,7 +392,7 @@ export class Network implements INetwork { ); } - async publishLightClientFinalityUpdate(update: allForks.LightClientFinalityUpdate): Promise { + async publishLightClientFinalityUpdate(update: LightClientFinalityUpdate): Promise { const fork = this.config.getForkName(update.signatureSlot); return this.publishGossip( {type: GossipType.light_client_finality_update, fork}, @@ -388,7 +400,7 @@ export class Network implements INetwork { ); } - async publishLightClientOptimisticUpdate(update: allForks.LightClientOptimisticUpdate): Promise { + async publishLightClientOptimisticUpdate(update: LightClientOptimisticUpdate): Promise { const fork = this.config.getForkName(update.signatureSlot); return this.publishGossip( {type: GossipType.light_client_optimistic_update, fork}, @@ -419,7 +431,7 @@ export class Network implements INetwork { async sendBeaconBlocksByRange( peerId: PeerIdStr, request: phase0.BeaconBlocksByRangeRequest - ): Promise[]> { + ): Promise[]> { return collectSequentialBlocksInRange( this.sendReqRespRequest( peerId, @@ -435,7 +447,7 @@ export class Network implements INetwork { async sendBeaconBlocksByRoot( peerId: PeerIdStr, request: phase0.BeaconBlocksByRootRequest - ): Promise[]> { + ): Promise[]> { return collectMaxResponseTypedWithBytes( this.sendReqRespRequest( peerId, @@ -449,21 +461,21 @@ export class Network implements INetwork { ); } - async sendLightClientBootstrap(peerId: PeerIdStr, request: Root): Promise { + async sendLightClientBootstrap(peerId: PeerIdStr, request: Root): Promise { return collectExactOneTyped( this.sendReqRespRequest(peerId, ReqRespMethod.LightClientBootstrap, [Version.V1], request), responseSszTypeByMethod[ReqRespMethod.LightClientBootstrap] ); } - async sendLightClientOptimisticUpdate(peerId: PeerIdStr): Promise { + async sendLightClientOptimisticUpdate(peerId: PeerIdStr): Promise { return collectExactOneTyped( this.sendReqRespRequest(peerId, ReqRespMethod.LightClientOptimisticUpdate, [Version.V1], null), responseSszTypeByMethod[ReqRespMethod.LightClientOptimisticUpdate] ); } - async sendLightClientFinalityUpdate(peerId: PeerIdStr): Promise { + async sendLightClientFinalityUpdate(peerId: PeerIdStr): Promise { return collectExactOneTyped( this.sendReqRespRequest(peerId, ReqRespMethod.LightClientFinalityUpdate, [Version.V1], null), responseSszTypeByMethod[ReqRespMethod.LightClientFinalityUpdate] @@ -473,7 +485,7 @@ export class Network implements INetwork { async sendLightClientUpdatesByRange( peerId: PeerIdStr, request: altair.LightClientUpdatesByRange - ): Promise { + ): Promise { return collectMaxResponseTyped( this.sendReqRespRequest(peerId, ReqRespMethod.LightClientUpdatesByRange, [Version.V1], request), request.count, @@ -571,7 +583,7 @@ export class Network implements INetwork { return this.core.writeDiscv5HeapSnapshot(prefix, dirpath); } - private onLightClientFinalityUpdate = async (finalityUpdate: allForks.LightClientFinalityUpdate): Promise => { + private onLightClientFinalityUpdate = async (finalityUpdate: LightClientFinalityUpdate): Promise => { // TODO: Review is OK to remove if (this.hasAttachedSyncCommitteeMember()) try { @@ -588,9 +600,7 @@ export class Network implements INetwork { } }; - private onLightClientOptimisticUpdate = async ( - optimisticUpdate: allForks.LightClientOptimisticUpdate - ): Promise => { + private onLightClientOptimisticUpdate = async (optimisticUpdate: LightClientOptimisticUpdate): Promise => { // TODO: Review is OK to remove if (this.hasAttachedSyncCommitteeMember()) try { diff --git a/packages/beacon-node/src/network/peers/peerManager.ts b/packages/beacon-node/src/network/peers/peerManager.ts index 5149b129c363..43fbee723f0e 100644 --- a/packages/beacon-node/src/network/peers/peerManager.ts +++ b/packages/beacon-node/src/network/peers/peerManager.ts @@ -2,7 +2,7 @@ import {Connection, PeerId} from "@libp2p/interface"; import {BitArray} from "@chainsafe/ssz"; import {SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params"; import {BeaconConfig} from "@lodestar/config"; -import {allForks, altair, phase0} from "@lodestar/types"; +import {Metadata, altair, phase0} from "@lodestar/types"; import {withTimeout} from "@lodestar/utils"; import {LoggerNode} from "@lodestar/logger/node"; import {GoodByeReasonCode, GOODBYE_KNOWN_CODES, Libp2pEvent} from "../../constants/index.js"; @@ -90,7 +90,7 @@ export interface IReqRespBeaconNodePeerManager { sendPing(peerId: PeerId): Promise; sendStatus(peerId: PeerId, request: phase0.Status): Promise; sendGoodbye(peerId: PeerId, request: phase0.Goodbye): Promise; - sendMetadata(peerId: PeerId): Promise; + sendMetadata(peerId: PeerId): Promise; } export type PeerManagerModules = { @@ -301,7 +301,7 @@ export class PeerManager { /** * Handle a METADATA request + response (rpc handler responds with METADATA automatically) */ - private onMetadata(peer: PeerId, metadata: allForks.Metadata): void { + private onMetadata(peer: PeerId, metadata: Metadata): void { // Store metadata always in case the peer updates attnets but not the sequence number // Trust that the peer always sends the latest metadata (From Lighthouse) const peerData = this.connectedPeers.get(peer.toString()); diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 1a71cc7de334..82fe7d8db358 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; import {BeaconConfig, ChainForkConfig} from "@lodestar/config"; import {LogLevel, Logger, prettyBytes} from "@lodestar/utils"; -import {Root, Slot, ssz, allForks, deneb, UintNum64} from "@lodestar/types"; +import {Root, Slot, ssz, deneb, UintNum64, SignedBeaconBlock} from "@lodestar/types"; import {ForkName, ForkSeq} from "@lodestar/params"; import {routes} from "@lodestar/api"; import {computeTimeAtSlot} from "@lodestar/state-transition"; @@ -113,7 +113,7 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler const {chain, config, metrics, events, logger, core, aggregatorTracker} = modules; async function validateBeaconBlock( - signedBlock: allForks.SignedBeaconBlock, + signedBlock: SignedBeaconBlock, blockBytes: Uint8Array, fork: ForkName, peerIdStr: string, diff --git a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts index cfe13b527183..8d131e9e9945 100644 --- a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts +++ b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts @@ -13,7 +13,7 @@ import { ResponseIncoming, ResponseOutgoing, } from "@lodestar/reqresp"; -import {allForks, phase0, ssz} from "@lodestar/types"; +import {Metadata, phase0, ssz} from "@lodestar/types"; import {Logger} from "@lodestar/utils"; import {INetworkEventBus, NetworkEvent} from "../events.js"; import {MetadataController} from "../metadata.js"; @@ -184,7 +184,7 @@ export class ReqRespBeaconNode extends ReqResp { ); } - async sendMetadata(peerId: PeerId): Promise { + async sendMetadata(peerId: PeerId): Promise { return collectExactOneTyped( this.sendReqRespRequest( peerId, diff --git a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts index ff5689a7b8c3..4dae4831d716 100644 --- a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts +++ b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts @@ -1,5 +1,5 @@ import {ChainForkConfig} from "@lodestar/config"; -import {deneb, Epoch, phase0, allForks, Slot} from "@lodestar/types"; +import {deneb, Epoch, phase0, SignedBeaconBlock, Slot} from "@lodestar/types"; import {ForkSeq} from "@lodestar/params"; import {computeEpochAtSlot} from "@lodestar/state-transition"; @@ -55,7 +55,7 @@ export async function beaconBlocksMaybeBlobsByRange( // Assumes that the blobs are in the same sequence as blocks, doesn't require block to be sorted export function matchBlockWithBlobs( config: ChainForkConfig, - allBlocks: WithBytes[], + allBlocks: WithBytes[], allBlobSidecars: deneb.BlobSidecar[], endSlot: Slot, blockSource: BlockSource, diff --git a/packages/beacon-node/src/network/reqresp/types.ts b/packages/beacon-node/src/network/reqresp/types.ts index f690d282307f..78625f6ea414 100644 --- a/packages/beacon-node/src/network/reqresp/types.ts +++ b/packages/beacon-node/src/network/reqresp/types.ts @@ -1,7 +1,7 @@ import {Type} from "@chainsafe/ssz"; import {ForkLightClient, ForkName, isForkLightClient} from "@lodestar/params"; import {Protocol, ProtocolHandler, ReqRespRequest} from "@lodestar/reqresp"; -import {Root, allForks, altair, deneb, phase0, ssz} from "@lodestar/types"; +import {Metadata, Root, SignedBeaconBlock, altair, deneb, phase0, ssz} from "@lodestar/types"; export type ProtocolNoHandler = Omit; @@ -42,10 +42,10 @@ type ResponseBodyByMethod = { [ReqRespMethod.Status]: phase0.Status; [ReqRespMethod.Goodbye]: phase0.Goodbye; [ReqRespMethod.Ping]: phase0.Ping; - [ReqRespMethod.Metadata]: allForks.Metadata; + [ReqRespMethod.Metadata]: Metadata; // Do not matter - [ReqRespMethod.BeaconBlocksByRange]: allForks.SignedBeaconBlock; - [ReqRespMethod.BeaconBlocksByRoot]: allForks.SignedBeaconBlock; + [ReqRespMethod.BeaconBlocksByRange]: SignedBeaconBlock; + [ReqRespMethod.BeaconBlocksByRoot]: SignedBeaconBlock; [ReqRespMethod.BlobSidecarsByRange]: deneb.BlobSidecar; [ReqRespMethod.BlobSidecarsByRoot]: deneb.BlobSidecar; [ReqRespMethod.LightClientBootstrap]: altair.LightClientBootstrap; @@ -74,7 +74,7 @@ export const requestSszTypeByMethod: { export type ResponseTypeGetter = (fork: ForkName, version: number) => Type; -const blocksResponseType: ResponseTypeGetter = (fork, version) => { +const blocksResponseType: ResponseTypeGetter = (fork, version) => { if (version === Version.V1) { return ssz.phase0.SignedBeaconBlock; } else { diff --git a/packages/beacon-node/src/network/reqresp/utils/collectSequentialBlocksInRange.ts b/packages/beacon-node/src/network/reqresp/utils/collectSequentialBlocksInRange.ts index e1a637c7df89..c2cf0ad16ea0 100644 --- a/packages/beacon-node/src/network/reqresp/utils/collectSequentialBlocksInRange.ts +++ b/packages/beacon-node/src/network/reqresp/utils/collectSequentialBlocksInRange.ts @@ -1,6 +1,6 @@ import {ResponseIncoming} from "@lodestar/reqresp"; -import {allForks, phase0} from "@lodestar/types"; import {LodestarError} from "@lodestar/utils"; +import {phase0, SignedBeaconBlock} from "@lodestar/types"; import {WithBytes} from "../../interface.js"; import {ReqRespMethod, responseSszTypeByMethod} from "../types.js"; import {sszDeserializeResponse} from "./collect.js"; @@ -12,8 +12,8 @@ import {sszDeserializeResponse} from "./collect.js"; export async function collectSequentialBlocksInRange( blockStream: AsyncIterable, {count, startSlot}: Pick -): Promise[]> { - const blocks: WithBytes[] = []; +): Promise[]> { + const blocks: WithBytes[] = []; for await (const chunk of blockStream) { const blockType = responseSszTypeByMethod[ReqRespMethod.BeaconBlocksByRange](chunk.fork, chunk.protocolVersion); diff --git a/packages/beacon-node/src/sync/backfill/backfill.ts b/packages/beacon-node/src/sync/backfill/backfill.ts index 7d06f1dc7b65..6d9716a37329 100644 --- a/packages/beacon-node/src/sync/backfill/backfill.ts +++ b/packages/beacon-node/src/sync/backfill/backfill.ts @@ -3,7 +3,7 @@ import {StrictEventEmitter} from "strict-event-emitter-types"; import {toHexString} from "@chainsafe/ssz"; import {BeaconStateAllForks, blockToHeader} from "@lodestar/state-transition"; import {BeaconConfig, ChainForkConfig} from "@lodestar/config"; -import {phase0, Root, Slot, allForks, ssz} from "@lodestar/types"; +import {phase0, Root, SignedBeaconBlock, Slot, ssz} from "@lodestar/types"; import {ErrorAborted, Logger, sleep, toHex} from "@lodestar/utils"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; @@ -94,7 +94,7 @@ type BackfillSyncEmitter = StrictEventEmitter; */ type BackFillSyncAnchor = | { - anchorBlock: allForks.SignedBeaconBlock; + anchorBlock: SignedBeaconBlock; anchorBlockRoot: Root; anchorSlot: Slot; lastBackSyncedBlock: BackfillBlock; diff --git a/packages/beacon-node/src/sync/backfill/verify.ts b/packages/beacon-node/src/sync/backfill/verify.ts index eba4feca48ef..462762a5576f 100644 --- a/packages/beacon-node/src/sync/backfill/verify.ts +++ b/packages/beacon-node/src/sync/backfill/verify.ts @@ -1,6 +1,6 @@ import {CachedBeaconStateAllForks, ISignatureSet, getBlockProposerSignatureSet} from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; -import {allForks, Root, allForks as allForkTypes, ssz, Slot} from "@lodestar/types"; +import {Root, ssz, Slot, SignedBeaconBlock} from "@lodestar/types"; import {GENESIS_SLOT} from "@lodestar/params"; import {IBlsVerifier} from "../../chain/bls/index.js"; import {WithBytes} from "../../network/interface.js"; @@ -11,21 +11,21 @@ export type BackfillBlockHeader = { root: Root; }; -export type BackfillBlock = BackfillBlockHeader & {block: allForks.SignedBeaconBlock}; +export type BackfillBlock = BackfillBlockHeader & {block: SignedBeaconBlock}; export function verifyBlockSequence( config: BeaconConfig, - blocks: WithBytes[], + blocks: WithBytes[], anchorRoot: Root ): { nextAnchor: BackfillBlock | null; - verifiedBlocks: WithBytes[]; + verifiedBlocks: WithBytes[]; error?: BackfillSyncErrorCode.NOT_LINEAR; } { let nextRoot: Root = anchorRoot; let nextAnchor: BackfillBlock | null = null; - const verifiedBlocks: WithBytes[] = []; + const verifiedBlocks: WithBytes[] = []; for (const block of blocks.reverse()) { const blockRoot = config.getForkTypes(block.data.message.slot).BeaconBlock.hashTreeRoot(block.data.message); if (!ssz.Root.equals(blockRoot, nextRoot)) { @@ -44,7 +44,7 @@ export function verifyBlockSequence( export async function verifyBlockProposerSignature( bls: IBlsVerifier, state: CachedBeaconStateAllForks, - blocks: WithBytes[] + blocks: WithBytes[] ): Promise { if (blocks.length === 1 && blocks[0].data.message.slot === GENESIS_SLOT) return; const signatures = blocks.reduce((sigs: ISignatureSet[], block) => { diff --git a/packages/beacon-node/src/util/blobs.ts b/packages/beacon-node/src/util/blobs.ts index 13d935ba29da..fcc31092464f 100644 --- a/packages/beacon-node/src/util/blobs.ts +++ b/packages/beacon-node/src/util/blobs.ts @@ -1,7 +1,7 @@ import {digest as sha256Digest} from "@chainsafe/as-sha256"; import {Tree} from "@chainsafe/persistent-merkle-tree"; -import {VERSIONED_HASH_VERSION_KZG, KZG_COMMITMENT_GINDEX0, ForkName} from "@lodestar/params"; -import {deneb, ssz, allForks} from "@lodestar/types"; +import {VERSIONED_HASH_VERSION_KZG, KZG_COMMITMENT_GINDEX0, ForkName, ForkAll} from "@lodestar/params"; +import {deneb, ssz, BeaconBlockBody, SignedBeaconBlock, SSZTypesFor} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {signedBlockToSignedHeader} from "@lodestar/state-transition"; @@ -16,17 +16,17 @@ export function kzgCommitmentToVersionedHash(kzgCommitment: deneb.KZGCommitment) export function computeInclusionProof( fork: ForkName, - body: allForks.BeaconBlockBody, + body: BeaconBlockBody, index: number ): deneb.KzgCommitmentInclusionProof { - const bodyView = (ssz[fork].BeaconBlockBody as allForks.AllForksSSZTypes["BeaconBlockBody"]).toView(body); + const bodyView = (ssz[fork].BeaconBlockBody as SSZTypesFor).toView(body); const commitmentGindex = KZG_COMMITMENT_GINDEX0 + index; return new Tree(bodyView.node).getSingleProof(BigInt(commitmentGindex)); } export function computeBlobSidecars( config: ChainForkConfig, - signedBlock: allForks.SignedBeaconBlock, + signedBlock: SignedBeaconBlock, contents: deneb.Contents & {kzgCommitmentInclusionProofs?: deneb.KzgCommitmentInclusionProof[]} ): deneb.BlobSidecars { const blobKzgCommitments = (signedBlock as deneb.SignedBeaconBlock).message.body.blobKzgCommitments; diff --git a/packages/beacon-node/src/util/multifork.ts b/packages/beacon-node/src/util/multifork.ts index 4abeacd2e566..0a00677afadf 100644 --- a/packages/beacon-node/src/util/multifork.ts +++ b/packages/beacon-node/src/util/multifork.ts @@ -1,6 +1,7 @@ import {ChainForkConfig} from "@lodestar/config"; -import {Slot, allForks} from "@lodestar/types"; +import {SSZTypesFor, Slot} from "@lodestar/types"; import {bytesToInt} from "@lodestar/utils"; +import {ForkAll, ForkLightClient} from "@lodestar/params"; import {getSlotFromSignedBeaconBlockSerialized} from "./sszBytes.js"; /** @@ -23,7 +24,7 @@ const SLOT_BYTES_POSITION_IN_STATE = 40; export function getSignedBlockTypeFromBytes( config: ChainForkConfig, bytes: Buffer | Uint8Array -): allForks.AllForksSSZTypes["SignedBeaconBlock"] { +): SSZTypesFor { const slot = getSlotFromSignedBeaconBlockSerialized(bytes); if (slot === null) { throw Error("getSignedBlockTypeFromBytes: invalid bytes"); @@ -35,7 +36,7 @@ export function getSignedBlockTypeFromBytes( export function getStateTypeFromBytes( config: ChainForkConfig, bytes: Buffer | Uint8Array -): allForks.AllForksSSZTypes["BeaconState"] { +): SSZTypesFor { const slot = getStateSlotFromBytes(bytes); return config.getForkTypes(slot).BeaconState; } @@ -60,7 +61,7 @@ const SLOT_BYTES_POSITION_IN_LIGHTCLIENTHEADER = 0; export function getLightClientHeaderTypeFromBytes( config: ChainForkConfig, bytes: Buffer | Uint8Array -): allForks.AllForksLightClientSSZTypes["LightClientHeader"] { +): SSZTypesFor { const slot = bytesToInt( bytes.subarray(SLOT_BYTES_POSITION_IN_LIGHTCLIENTHEADER, SLOT_BYTES_POSITION_IN_LIGHTCLIENTHEADER + SLOT_BYTE_COUNT) ); diff --git a/packages/beacon-node/test/e2e/doppelganger/doppelganger.test.ts b/packages/beacon-node/test/e2e/doppelganger/doppelganger.test.ts index 8a1d1585ec2d..ab9061f1eacd 100644 --- a/packages/beacon-node/test/e2e/doppelganger/doppelganger.test.ts +++ b/packages/beacon-node/test/e2e/doppelganger/doppelganger.test.ts @@ -88,7 +88,7 @@ describe.skip("doppelganger / doppelganger test", function () { const validatorUnderTest = validatorsWithDoppelganger[0]; const pubKey = validatorUnderTest.validatorStore.votingPubkeys()[0]; - const beaconBlock = ssz.allForks.phase0.BeaconBlock.defaultValue(); + const beaconBlock = ssz.phase0.BeaconBlock.defaultValue(); await expect( validatorUnderTest.validatorStore.signBlock(fromHexString(pubKey), beaconBlock, bn.chain.clock.currentSlot) @@ -241,7 +241,7 @@ describe.skip("doppelganger / doppelganger test", function () { const validatorUnderTest = validatorsWithDoppelganger[0]; const pubKey = validatorUnderTest.validatorStore.votingPubkeys()[0]; - const beaconBlock = ssz.allForks.phase0.BeaconBlock.defaultValue(); + const beaconBlock = ssz.phase0.BeaconBlock.defaultValue(); await expect( validatorUnderTest.validatorStore.signBlock(fromHexString(pubKey), beaconBlock, bn.chain.clock.currentSlot) diff --git a/packages/beacon-node/test/e2e/network/reqresp.test.ts b/packages/beacon-node/test/e2e/network/reqresp.test.ts index 38b5cda4db5f..7969282194dd 100644 --- a/packages/beacon-node/test/e2e/network/reqresp.test.ts +++ b/packages/beacon-node/test/e2e/network/reqresp.test.ts @@ -3,7 +3,7 @@ import {createChainForkConfig, ChainForkConfig} from "@lodestar/config"; import {chainConfig} from "@lodestar/config/default"; import {ForkName} from "@lodestar/params"; import {RequestError, RequestErrorCode, ResponseOutgoing} from "@lodestar/reqresp"; -import {allForks, altair, phase0, Root, ssz} from "@lodestar/types"; +import {altair, phase0, Root, SignedBeaconBlock, ssz} from "@lodestar/types"; import {sleep as _sleep} from "@lodestar/utils"; import {Network, ReqRespBeaconNodeOpts} from "../../../src/network/index.js"; import {expectRejectedWithLodestarError} from "../../utils/errors.js"; @@ -328,7 +328,7 @@ function getEmptyEncodedPayloadSignedBeaconBlock(config: ChainForkConfig): Respo return wrapBlockAsEncodedPayload(config, config.getForkTypes(0).SignedBeaconBlock.defaultValue()); } -function wrapBlockAsEncodedPayload(config: ChainForkConfig, block: allForks.SignedBeaconBlock): ResponseOutgoing { +function wrapBlockAsEncodedPayload(config: ChainForkConfig, block: SignedBeaconBlock): ResponseOutgoing { return { data: config.getForkTypes(block.message.slot).SignedBeaconBlock.serialize(block), fork: config.getForkName(block.message.slot), diff --git a/packages/beacon-node/test/mocks/fork-choice/timeliness.ts b/packages/beacon-node/test/mocks/fork-choice/timeliness.ts index 72b3ff66a084..f5fe66902dee 100644 --- a/packages/beacon-node/test/mocks/fork-choice/timeliness.ts +++ b/packages/beacon-node/test/mocks/fork-choice/timeliness.ts @@ -1,5 +1,5 @@ import {ForkChoice} from "@lodestar/fork-choice"; -import {Slot, allForks} from "@lodestar/types"; +import {BeaconBlock, Slot} from "@lodestar/types"; /** * A specific forkchoice implementation to mark some blocks as timely or not. @@ -14,7 +14,7 @@ export class TimelinessForkChoice extends ForkChoice { /** * This is to mark the `lateSlot` as not timely. */ - protected isBlockTimely(block: allForks.BeaconBlock, blockDelaySec: number): boolean { + protected isBlockTimely(block: BeaconBlock, blockDelaySec: number): boolean { if (block.slot === this.lateSlot) { return false; } diff --git a/packages/beacon-node/test/sim/mergemock.test.ts b/packages/beacon-node/test/sim/mergemock.test.ts index 3705b845d805..ee9839d58822 100644 --- a/packages/beacon-node/test/sim/mergemock.test.ts +++ b/packages/beacon-node/test/sim/mergemock.test.ts @@ -5,7 +5,7 @@ import {LogLevel, sleep} from "@lodestar/utils"; import {TimestampFormatCode} from "@lodestar/logger"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {ChainConfig} from "@lodestar/config"; -import {Epoch, allForks, bellatrix} from "@lodestar/types"; +import {Epoch, SignedBeaconBlock, bellatrix} from "@lodestar/types"; import {ValidatorProposerConfig} from "@lodestar/validator"; import {routes} from "@lodestar/api"; @@ -213,7 +213,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { await new Promise((resolve, _reject) => { bn.chain.emitter.on(routes.events.EventType.block, async (blockData) => { const {data: fullOrBlindedBlock} = (await bn.api.beacon.getBlockV2({blockId: blockData.block})) as { - data: allForks.SignedBeaconBlock; + data: SignedBeaconBlock; }; if (fullOrBlindedBlock !== undefined) { const blockFeeRecipient = toHexString( diff --git a/packages/beacon-node/test/spec/presets/fork_choice.test.ts b/packages/beacon-node/test/spec/presets/fork_choice.test.ts index 67cb35972d77..92862c6cb03b 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -4,7 +4,7 @@ import {toHexString} from "@chainsafe/ssz"; import {BeaconStateAllForks, isExecutionStateType, signedBlockToSignedHeader} from "@lodestar/state-transition"; import {InputType} from "@lodestar/spec-test-util"; import {CheckpointWithHex, ForkChoice} from "@lodestar/fork-choice"; -import {phase0, allForks, bellatrix, ssz, RootHex, deneb} from "@lodestar/types"; +import {phase0, bellatrix, ssz, RootHex, deneb, BeaconBlock, SignedBeaconBlock} from "@lodestar/types"; import {bnToNum, fromHex} from "@lodestar/utils"; import {createBeaconConfig} from "@lodestar/config"; import {ACTIVE_PRESET, ForkSeq, isForkBlobs, ForkName} from "@lodestar/params"; @@ -345,7 +345,7 @@ const forkChoiceTest = }, mapToTestCase: (t: Record) => { // t has input file name as key - const blocks = new Map(); + const blocks = new Map(); const blobs = new Map(); const powBlocks = new Map(); const attestations = new Map(); @@ -487,9 +487,9 @@ type ForkChoiceTestCase = { bls_setting: bigint; }; anchorState: BeaconStateAllForks; - anchorBlock: allForks.BeaconBlock; + anchorBlock: BeaconBlock; steps: Step[]; - blocks: Map; + blocks: Map; blobs: Map; powBlocks: Map; attestations: Map; diff --git a/packages/beacon-node/test/spec/presets/genesis.test.ts b/packages/beacon-node/test/spec/presets/genesis.test.ts index ba3351a2103c..00ba11bc1e8e 100644 --- a/packages/beacon-node/test/spec/presets/genesis.test.ts +++ b/packages/beacon-node/test/spec/presets/genesis.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; import {expect} from "vitest"; -import {phase0, Root, ssz, TimeSeconds, allForks, deneb} from "@lodestar/types"; +import {phase0, Root, ssz, TimeSeconds, ExecutionPayloadHeader} from "@lodestar/types"; import {InputType} from "@lodestar/spec-test-util"; import { BeaconStateAllForks, @@ -60,7 +60,9 @@ const genesisInitialization: TestRunnerFn + ) ); }, // eth1.yaml @@ -141,7 +143,7 @@ type GenesisInitSpecTest = { meta: { deposits_count: number; }; - execution_payload_header?: allForks.ExecutionPayloadHeader; + execution_payload_header?: ExecutionPayloadHeader; state: BeaconStateAllForks; }; diff --git a/packages/beacon-node/test/spec/presets/light_client/update_ranking.ts b/packages/beacon-node/test/spec/presets/light_client/update_ranking.ts index 9a38ddf36c8c..3ed71d6ff164 100644 --- a/packages/beacon-node/test/spec/presets/light_client/update_ranking.ts +++ b/packages/beacon-node/test/spec/presets/light_client/update_ranking.ts @@ -1,5 +1,5 @@ import {expect} from "vitest"; -import {altair, ssz, allForks} from "@lodestar/types"; +import {LightClientUpdate, altair, ssz} from "@lodestar/types"; import {isForkLightClient} from "@lodestar/params"; import {InputType} from "@lodestar/spec-test-util"; import {isBetterUpdate, LightClientUpdateSummary, toLightClientUpdateSummary} from "@lodestar/light-client/spec"; @@ -22,7 +22,7 @@ export const updateRanking: TestRunnerFn = (fork) = testFunction: (testcase) => { // Parse update files const updatesCount = Number(testcase.meta.updates_count as bigint); - const updates: allForks.LightClientUpdate[] = []; + const updates: LightClientUpdate[] = []; for (let i = 0; i < updatesCount; i++) { const update = (testcase as unknown as Record)[`updates_${i}`]; diff --git a/packages/beacon-node/test/spec/presets/sanity.test.ts b/packages/beacon-node/test/spec/presets/sanity.test.ts index 57afb8cf3d28..3ec1efb84fde 100644 --- a/packages/beacon-node/test/spec/presets/sanity.test.ts +++ b/packages/beacon-node/test/spec/presets/sanity.test.ts @@ -7,7 +7,7 @@ import { processSlots, stateTransition, } from "@lodestar/state-transition"; -import {allForks, deneb, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, deneb, ssz} from "@lodestar/types"; import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; import {bnToNum} from "@lodestar/utils"; import {createCachedBeaconStateTest} from "../../utils/cachedBeaconState.js"; @@ -107,7 +107,7 @@ export function generateBlocksSZZTypeMapping(fork: ForkName, n: number): BlocksS } type SanityBlocksTestCase = { - [k: string]: allForks.SignedBeaconBlock | unknown | null | undefined; + [k: string]: SignedBeaconBlock | unknown | null | undefined; meta: { blocks_count: number; bls_setting: bigint; diff --git a/packages/beacon-node/test/spec/presets/transition.test.ts b/packages/beacon-node/test/spec/presets/transition.test.ts index 77919d76c3b1..d9925f292677 100644 --- a/packages/beacon-node/test/spec/presets/transition.test.ts +++ b/packages/beacon-node/test/spec/presets/transition.test.ts @@ -5,7 +5,7 @@ import { ExecutionPayloadStatus, stateTransition, } from "@lodestar/state-transition"; -import {allForks, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, ssz} from "@lodestar/types"; import {createChainForkConfig, ChainConfig} from "@lodestar/config"; import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; import {bnToNum} from "@lodestar/utils"; @@ -53,7 +53,7 @@ const transition = let state = createCachedBeaconStateTest(testcase.pre, testConfig); for (let i = 0; i < meta.blocks_count; i++) { - const signedBlock = testcase[`blocks_${i}`] as allForks.SignedBeaconBlock; + const signedBlock = testcase[`blocks_${i}`] as SignedBeaconBlock; state = stateTransition(state, signedBlock, { // Assume valid and available for this test executionPayloadStatus: ExecutionPayloadStatus.valid, @@ -108,7 +108,7 @@ function getTransitionConfig(fork: ForkName, forkEpoch: number): Partial; type TransitionTestCase = { - [k: string]: allForks.SignedBeaconBlock | unknown | null | undefined; + [k: string]: SignedBeaconBlock | unknown | null | undefined; meta: { post_fork: ForkName; fork_epoch: bigint; diff --git a/packages/beacon-node/test/spec/utils/expectEqualBeaconState.ts b/packages/beacon-node/test/spec/utils/expectEqualBeaconState.ts index 1f22c1b3d8cc..160310160054 100644 --- a/packages/beacon-node/test/spec/utils/expectEqualBeaconState.ts +++ b/packages/beacon-node/test/spec/utils/expectEqualBeaconState.ts @@ -1,6 +1,6 @@ import {expect} from "vitest"; -import {allForks, ssz} from "@lodestar/types"; -import {ForkName} from "@lodestar/params"; +import {SSZTypesFor, ssz} from "@lodestar/types"; +import {ForkAll, ForkName} from "@lodestar/params"; import {InputType} from "@lodestar/spec-test-util"; import {BeaconStateAllForks} from "@lodestar/state-transition"; @@ -14,7 +14,7 @@ export function expectEqualBeaconState( const expected = expectedView.toValue(); const actual = actualView.toValue(); - const stateType = ssz[fork].BeaconState as allForks.AllForksSSZTypes["BeaconState"]; + const stateType = ssz[fork].BeaconState as SSZTypesFor; if (!stateType.equals(actual, expected)) { expect(stateType.toJson(actual)).to.deep.equal(stateType.toJson(expected)); throw Error("Wrong state"); diff --git a/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts b/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts index e2af3ecd5cae..a45678e5bf48 100644 --- a/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts +++ b/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts @@ -4,7 +4,7 @@ import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {toHex} from "@lodestar/utils"; import {ChainForkConfig} from "@lodestar/config"; -import {allForks, Slot, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, Slot, ssz} from "@lodestar/types"; import {verifyBlocksSanityChecks as verifyBlocksImportSanityChecks} from "../../../../src/chain/blocks/verifyBlocksSanityChecks.js"; import {BlockErrorCode} from "../../../../src/chain/errors/index.js"; import {expectThrowsLodestarError} from "../../../utils/errors.js"; @@ -17,7 +17,7 @@ describe("chain / blocks / verifyBlocksSanityChecks", function () { let forkChoice: MockedBeaconChain["forkChoice"]; let clock: ClockStopped; let modules: {forkChoice: IForkChoice; clock: IClock; config: ChainForkConfig}; - let block: allForks.SignedBeaconBlock; + let block: SignedBeaconBlock; const currentSlot = 1; beforeEach(() => { @@ -121,9 +121,9 @@ describe("chain / blocks / verifyBlocksSanityChecks", function () { */ function verifyBlocksSanityChecks( modules: Parameters[0], - blocks: allForks.SignedBeaconBlock[], + blocks: SignedBeaconBlock[], opts: Parameters[2] -): {relevantBlocks: allForks.SignedBeaconBlock[]; parentSlots: Slot[]; parentBlock: ProtoBlock | null} { +): {relevantBlocks: SignedBeaconBlock[]; parentSlots: Slot[]; parentBlock: ProtoBlock | null} { const {relevantBlocks, parentSlots, parentBlock} = verifyBlocksImportSanityChecks( modules, blocks.map((block) => getBlockInput.preData(config, block, BlockSource.byRange, null)), @@ -136,8 +136,8 @@ function verifyBlocksSanityChecks( }; } -function getValidChain(count: number, initialSlot = 0): allForks.SignedBeaconBlock[] { - const blocks: allForks.SignedBeaconBlock[] = []; +function getValidChain(count: number, initialSlot = 0): SignedBeaconBlock[] { + const blocks: SignedBeaconBlock[] = []; for (let i = 0; i < count; i++) { const block = ssz.phase0.SignedBeaconBlock.defaultValue(); @@ -154,7 +154,7 @@ function getValidChain(count: number, initialSlot = 0): allForks.SignedBeaconBlo return blocks; } -function getForkChoice(knownBlocks: allForks.SignedBeaconBlock[], finalizedEpoch = 0): IForkChoice { +function getForkChoice(knownBlocks: SignedBeaconBlock[], finalizedEpoch = 0): IForkChoice { const blocks = new Map(); for (const block of knownBlocks) { const protoBlock = toProtoBlock(block); @@ -174,7 +174,7 @@ function getForkChoice(knownBlocks: allForks.SignedBeaconBlock[], finalizedEpoch } as Partial as IForkChoice; } -function toProtoBlock(block: allForks.SignedBeaconBlock): ProtoBlock { +function toProtoBlock(block: SignedBeaconBlock): ProtoBlock { return { slot: block.message.slot, blockRoot: toHex(ssz.phase0.BeaconBlock.hashTreeRoot(block.message)), @@ -183,17 +183,17 @@ function toProtoBlock(block: allForks.SignedBeaconBlock): ProtoBlock { } as Partial as ProtoBlock; } -function slots(blocks: allForks.SignedBeaconBlock[]): Slot[] { +function slots(blocks: SignedBeaconBlock[]): Slot[] { return blocks.map((block) => block.message.slot); } /** Since blocks have no meaning compare the indexes against `allBlocks` */ function expectBlocks( - expectedBlocks: allForks.SignedBeaconBlock[], - actualBlocks: allForks.SignedBeaconBlock[], - allBlocks: allForks.SignedBeaconBlock[] + expectedBlocks: SignedBeaconBlock[], + actualBlocks: SignedBeaconBlock[], + allBlocks: SignedBeaconBlock[] ): void { - function indexOfBlocks(blocks: allForks.SignedBeaconBlock[]): number[] { + function indexOfBlocks(blocks: SignedBeaconBlock[]): number[] { return blocks.map((block) => allBlocks.indexOf(block)); } diff --git a/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts b/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts index 5ec991010466..a9a5edc9ec0e 100644 --- a/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts +++ b/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts @@ -1,11 +1,11 @@ import {describe, it, expect, beforeEach} from "vitest"; -import {ssz, allForks} from "@lodestar/types"; +import {LightClientHeader, ssz} from "@lodestar/types"; import {ForkName, ForkSeq} from "@lodestar/params"; import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {upgradeLightClientHeader} from "@lodestar/light-client/spec"; describe("UpgradeLightClientHeader", function () { - let lcHeaderByFork: Record; + let lcHeaderByFork: Record; let testSlots: Record; /* eslint-disable @typescript-eslint/naming-convention */ 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 ad2f2e690ddd..74f5248dd4b6 100644 --- a/packages/beacon-node/test/unit/chain/validation/block.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/block.test.ts @@ -2,7 +2,7 @@ import {Mock, Mocked, beforeEach, describe, it, vi} from "vitest"; import {config} from "@lodestar/config/default"; import {ProtoBlock} from "@lodestar/fork-choice"; import {ForkName} from "@lodestar/params"; -import {allForks, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, ssz} from "@lodestar/types"; import {MockedBeaconChain, getMockedBeaconChain} from "../../../mocks/mockedBeaconChain.js"; import {BlockErrorCode} from "../../../../src/chain/errors/index.js"; import {QueuedStateRegenerator} from "../../../../src/chain/regen/index.js"; @@ -17,7 +17,7 @@ describe("gossip block validation", function () { let forkChoice: MockedBeaconChain["forkChoice"]; let regen: Mocked; let verifySignature: Mock<[boolean]>; - let job: allForks.SignedBeaconBlock; + let job: SignedBeaconBlock; const proposerIndex = 0; const clockSlot = 32; const block = ssz.phase0.BeaconBlock.defaultValue(); diff --git a/packages/beacon-node/test/unit/network/reqresp/collectSequentialBlocksInRange.test.ts b/packages/beacon-node/test/unit/network/reqresp/collectSequentialBlocksInRange.test.ts index d577b6d6c3ee..fa9b47ebf026 100644 --- a/packages/beacon-node/test/unit/network/reqresp/collectSequentialBlocksInRange.test.ts +++ b/packages/beacon-node/test/unit/network/reqresp/collectSequentialBlocksInRange.test.ts @@ -1,5 +1,5 @@ import {describe, it, expect} from "vitest"; -import {allForks, phase0, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types"; import {ResponseIncoming} from "@lodestar/reqresp"; import {ForkName} from "@lodestar/params"; import { @@ -79,7 +79,7 @@ describe("beacon-node / network / reqresp / utils / collectSequentialBlocksInRan }); } - async function* arrToSource(arr: allForks.SignedBeaconBlock[]): AsyncGenerator { + async function* arrToSource(arr: SignedBeaconBlock[]): AsyncGenerator { for (const item of arr) { yield {data: ssz.phase0.SignedBeaconBlock.serialize(item), fork: ForkName.phase0, protocolVersion: 1}; } diff --git a/packages/beacon-node/test/utils/node/simTest.ts b/packages/beacon-node/test/utils/node/simTest.ts index 4bf922cfb377..e94c32cefb10 100644 --- a/packages/beacon-node/test/utils/node/simTest.ts +++ b/packages/beacon-node/test/utils/node/simTest.ts @@ -7,7 +7,7 @@ import { } from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; import {SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; -import {allForks, Epoch, Slot} from "@lodestar/types"; +import {BeaconBlock, Epoch, Slot} from "@lodestar/types"; import {Checkpoint} from "@lodestar/types/phase0"; import {Logger, mapValues} from "@lodestar/utils"; import {routes} from "@lodestar/api"; @@ -93,14 +93,14 @@ export function simTestInfoTracker(bn: BeaconNode, logger: Logger): () => void { }; } -function sumAttestationBits(block: allForks.BeaconBlock): number { +function sumAttestationBits(block: BeaconBlock): number { return Array.from(block.body.attestations).reduce( (total, att) => total + att.aggregationBits.getTrueBitIndexes().length, 0 ); } -function avgInclusionDelay(block: allForks.BeaconBlock): number { +function avgInclusionDelay(block: BeaconBlock): number { const inclDelay = Array.from(block.body.attestations).map((att) => block.slot - att.data.slot); return avg(inclDelay); } diff --git a/packages/beacon-node/test/utils/state.ts b/packages/beacon-node/test/utils/state.ts index af9ebd479340..10a653bf9498 100644 --- a/packages/beacon-node/test/utils/state.ts +++ b/packages/beacon-node/test/utils/state.ts @@ -8,7 +8,7 @@ import { CachedBeaconStateBellatrix, BeaconStateBellatrix, } from "@lodestar/state-transition"; -import {allForks, altair, bellatrix, ssz} from "@lodestar/types"; +import {BeaconState, altair, bellatrix, ssz} from "@lodestar/types"; import {createBeaconConfig, ChainForkConfig} from "@lodestar/config"; import {FAR_FUTURE_EPOCH, ForkName, ForkSeq, MAX_EFFECTIVE_BALANCE, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; @@ -20,7 +20,7 @@ import {getConfig} from "./config.js"; /** * Copy of BeaconState, but all fields are marked optional to allow for swapping out variables as needed. */ -type TestBeaconState = Partial; +type TestBeaconState = Partial; /** * Generate beaconState, by default it will generate a mostly empty state with "just enough" to be valid-ish diff --git a/packages/cli/test/utils/crucible/interfaces.ts b/packages/cli/test/utils/crucible/interfaces.ts index c406d5a72432..44358561cea5 100644 --- a/packages/cli/test/utils/crucible/interfaces.ts +++ b/packages/cli/test/utils/crucible/interfaces.ts @@ -6,7 +6,7 @@ import {ApiClient} from "@lodestar/api"; import {ApiClient as KeyManagerApi} from "@lodestar/api/keymanager"; import {ChainForkConfig} from "@lodestar/config"; import {ForkName} from "@lodestar/params"; -import {Slot, allForks, Epoch} from "@lodestar/types"; +import {Slot, Epoch, SignedBeaconBlock} from "@lodestar/types"; import {LogLevel, Logger} from "@lodestar/logger"; import {BeaconArgs} from "../../../src/cmds/beacon/options.js"; import {IValidatorCliArgs} from "../../../src/cmds/validator/options.js"; @@ -317,7 +317,7 @@ export interface AssertionInput { } export interface CaptureInput> extends AssertionInput { - block: allForks.SignedBeaconBlock; + block: SignedBeaconBlock; dependantStores: D; } diff --git a/packages/cli/test/utils/crucible/utils/network.ts b/packages/cli/test/utils/crucible/utils/network.ts index 6042e91bc221..e3555fc149b9 100644 --- a/packages/cli/test/utils/crucible/utils/network.ts +++ b/packages/cli/test/utils/crucible/utils/network.ts @@ -1,5 +1,5 @@ /* eslint-disable no-console */ -import {Slot, allForks} from "@lodestar/types"; +import {SignedBeaconBlock, Slot} from "@lodestar/types"; import {sleep} from "@lodestar/utils"; import {BeaconClient, BeaconNode, ExecutionClient, ExecutionNode, NodePair} from "../interfaces.js"; import {Simulation} from "../simulation.js"; @@ -155,7 +155,7 @@ export async function waitForSlot( export async function fetchBlock( node: NodePair, {tries, delay, slot, signal}: {slot: number; tries: number; delay: number; signal?: AbortSignal} -): Promise { +): Promise { for (let i = 0; i < tries; i++) { const res = await node.beacon.api.beacon.getBlockV2({blockId: slot}); if (!res.ok) { diff --git a/packages/cli/test/utils/crucible/utils/syncing.ts b/packages/cli/test/utils/crucible/utils/syncing.ts index 35b889fb3ac1..4a8913105335 100644 --- a/packages/cli/test/utils/crucible/utils/syncing.ts +++ b/packages/cli/test/utils/crucible/utils/syncing.ts @@ -1,6 +1,7 @@ import {routes} from "@lodestar/api"; -import {Slot} from "@lodestar/types"; +import {SignedBeaconBlock, Slot} from "@lodestar/types"; import {sleep, toHex} from "@lodestar/utils"; +import {ForkBlobs} from "@lodestar/params"; import type {Simulation} from "../simulation.js"; import {BeaconClient, ExecutionClient, NodePair} from "../interfaces.js"; import {connectNewCLNode, connectNewELNode, connectNewNode, waitForHead, waitForSlot} from "./network.js"; @@ -131,7 +132,7 @@ export async function assertUnknownBlockSync(env: Simulation): Promise { ( await unknownBlockSync.beacon.api.beacon.publishBlockV2({ signedBlockOrContents: { - signedBlock: currentHead, + signedBlock: currentHead as SignedBeaconBlock, blobs: currentSidecars.map((b) => b.blob), kzgProofs: currentSidecars.map((b) => b.kzgProof), }, diff --git a/packages/config/src/forkConfig/index.ts b/packages/config/src/forkConfig/index.ts index d630f1ddfc88..45213d0a611d 100644 --- a/packages/config/src/forkConfig/index.ts +++ b/packages/config/src/forkConfig/index.ts @@ -6,8 +6,12 @@ import { isForkLightClient, isForkExecution, isForkBlobs, + ForkExecution, + ForkAll, + ForkLightClient, + ForkBlobs, } from "@lodestar/params"; -import {Slot, allForks, Version, ssz} from "@lodestar/types"; +import {Slot, Version, ssz, SSZTypesFor, sszTypesFor} from "@lodestar/types"; import {ChainConfig} from "../chainConfig/index.js"; import {ForkConfig, ForkInfo} from "./types.js"; @@ -87,36 +91,36 @@ export function createForkConfig(config: ChainConfig): ForkConfig { getForkVersion(slot: Slot): Version { return this.getForkInfo(slot).version; }, - getForkTypes(slot: Slot): allForks.AllForksSSZTypes { - return ssz.allForks[this.getForkName(slot)] as allForks.AllForksSSZTypes; + getForkTypes(slot: Slot): SSZTypesFor { + return sszTypesFor(this.getForkName(slot)) as SSZTypesFor; }, - getExecutionForkTypes(slot: Slot): allForks.AllForksExecutionSSZTypes { + getExecutionForkTypes(slot: Slot): SSZTypesFor { const forkName = this.getForkName(slot); if (!isForkExecution(forkName)) { throw Error(`Invalid slot=${slot} fork=${forkName} for execution fork types`); } - return ssz.allForksExecution[forkName] as allForks.AllForksExecutionSSZTypes; + return ssz.allForksExecution[forkName]; }, - getBlindedForkTypes(slot: Slot): allForks.AllForksBlindedSSZTypes { + getBlindedForkTypes(slot: Slot): (typeof ssz.allForksBlinded)[ForkExecution] { const forkName = this.getForkName(slot); if (!isForkExecution(forkName)) { throw Error(`Invalid slot=${slot} fork=${forkName} for blinded fork types`); } - return ssz.allForksBlinded[forkName] as allForks.AllForksBlindedSSZTypes; + return ssz.allForksBlinded[forkName]; }, - getLightClientForkTypes(slot: Slot): allForks.AllForksLightClientSSZTypes { + getLightClientForkTypes(slot: Slot): SSZTypesFor { const forkName = this.getForkName(slot); if (!isForkLightClient(forkName)) { throw Error(`Invalid slot=${slot} fork=${forkName} for lightclient fork types`); } - return ssz.allForksLightClient[forkName] as allForks.AllForksLightClientSSZTypes; + return ssz.allForksLightClient[forkName]; }, - getBlobsForkTypes(slot: Slot): allForks.AllForksBlobsSSZTypes { + getBlobsForkTypes(slot: Slot): SSZTypesFor { const forkName = this.getForkName(slot); if (!isForkBlobs(forkName)) { throw Error(`Invalid slot=${slot} fork=${forkName} for blobs fork types`); } - return ssz.allForksBlobs[forkName] as allForks.AllForksBlobsSSZTypes; + return ssz.allForksBlobs[forkName]; }, }; } diff --git a/packages/config/src/forkConfig/types.ts b/packages/config/src/forkConfig/types.ts index b61752bf3698..93f34a62bc83 100644 --- a/packages/config/src/forkConfig/types.ts +++ b/packages/config/src/forkConfig/types.ts @@ -1,5 +1,5 @@ -import {ForkName, ForkSeq} from "@lodestar/params"; -import {allForks, Epoch, Slot, Version} from "@lodestar/types"; +import {ForkAll, ForkBlobs, ForkExecution, ForkLightClient, ForkName, ForkSeq} from "@lodestar/params"; +import {Epoch, SSZBlindedTypesFor, SSZTypesFor, Slot, Version} from "@lodestar/types"; export type ForkInfo = { name: ForkName; @@ -29,13 +29,13 @@ export type ForkConfig = { /** Get the hard-fork version at a given slot */ getForkVersion(slot: Slot): Version; /** Get SSZ types by hard-fork */ - getForkTypes(slot: Slot): allForks.AllForksSSZTypes; + getForkTypes(slot: Slot): SSZTypesFor; /** Get lightclient SSZ types by hard-fork*/ - getLightClientForkTypes(slot: Slot): allForks.AllForksLightClientSSZTypes; + getLightClientForkTypes(slot: Slot): SSZTypesFor; /** Get execution SSZ types by hard-fork*/ - getExecutionForkTypes(slot: Slot): allForks.AllForksExecutionSSZTypes; + getExecutionForkTypes(slot: Slot): SSZTypesFor; /** Get blinded SSZ types by hard-fork */ - getBlindedForkTypes(slot: Slot): allForks.AllForksBlindedSSZTypes; + getBlindedForkTypes(slot: Slot): SSZBlindedTypesFor; /** Get blobs SSZ types by hard-fork*/ - getBlobsForkTypes(slot: Slot): allForks.AllForksBlobsSSZTypes; + getBlobsForkTypes(slot: Slot): SSZTypesFor; }; diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 2433d68224f2..2e2abed95eb7 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; import {Logger, fromHex} from "@lodestar/utils"; import {SLOTS_PER_HISTORICAL_ROOT, SLOTS_PER_EPOCH, INTERVALS_PER_SLOT} from "@lodestar/params"; -import {bellatrix, Slot, ValidatorIndex, phase0, allForks, ssz, RootHex, Epoch, Root} from "@lodestar/types"; +import {bellatrix, Slot, ValidatorIndex, phase0, ssz, RootHex, Epoch, Root, BeaconBlock} from "@lodestar/types"; import { computeSlotsSinceEpochStart, computeStartSlotAtEpoch, @@ -464,7 +464,7 @@ export class ForkChoice implements IForkChoice { * This ensures that the forkchoice is never out of sync. */ onBlock( - block: allForks.BeaconBlock, + block: BeaconBlock, state: CachedBeaconStateAllForks, blockDelaySec: number, currentSlot: Slot, @@ -1088,7 +1088,7 @@ export class ForkChoice implements IForkChoice { * Return true if the block is timely for the current slot. * Child class can overwrite this for testing purpose. */ - protected isBlockTimely(block: allForks.BeaconBlock, blockDelaySec: number): boolean { + protected isBlockTimely(block: BeaconBlock, blockDelaySec: number): boolean { const isBeforeAttestingInterval = blockDelaySec < this.config.SECONDS_PER_SLOT / INTERVALS_PER_SLOT; return this.fcStore.currentSlot === block.slot && isBeforeAttestingInterval; } diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index aa5b86f0e64e..d0629c2125cc 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -1,6 +1,6 @@ import {EffectiveBalanceIncrements} from "@lodestar/state-transition"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; -import {Epoch, Slot, ValidatorIndex, phase0, allForks, Root, RootHex} from "@lodestar/types"; +import {Epoch, Slot, ValidatorIndex, phase0, Root, RootHex, BeaconBlock} from "@lodestar/types"; import { ProtoBlock, MaybeValidExecutionStatus, @@ -131,7 +131,7 @@ export interface IForkChoice { * The supplied block **must** pass the `state_transition` function as it will not be run here. */ onBlock( - block: allForks.BeaconBlock, + block: BeaconBlock, state: CachedBeaconStateAllForks, blockDelaySec: number, currentSlot: Slot, diff --git a/packages/light-client/src/events.ts b/packages/light-client/src/events.ts index 9bb6bcb0b591..5b561718e89e 100644 --- a/packages/light-client/src/events.ts +++ b/packages/light-client/src/events.ts @@ -1,4 +1,4 @@ -import {allForks} from "@lodestar/types"; +import {LightClientHeader} from "@lodestar/types"; import {RunStatusCode} from "./index.js"; export enum LightclientEvent { @@ -8,8 +8,8 @@ export enum LightclientEvent { } export type LightclientEmitterEvents = { - [LightclientEvent.lightClientOptimisticHeader]: (newHeader: allForks.LightClientHeader) => void; - [LightclientEvent.lightClientFinalityHeader]: (newHeader: allForks.LightClientHeader) => void; + [LightclientEvent.lightClientOptimisticHeader]: (newHeader: LightClientHeader) => void; + [LightclientEvent.lightClientFinalityHeader]: (newHeader: LightClientHeader) => void; [LightclientEvent.statusChange]: (code: RunStatusCode) => void; }; diff --git a/packages/light-client/src/index.ts b/packages/light-client/src/index.ts index 63996a8fda18..16ecf1adf939 100644 --- a/packages/light-client/src/index.ts +++ b/packages/light-client/src/index.ts @@ -1,7 +1,17 @@ import mitt from "mitt"; import {fromHexString, toHexString} from "@chainsafe/ssz"; import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD} from "@lodestar/params"; -import {phase0, RootHex, Slot, SyncPeriod, allForks} from "@lodestar/types"; +import { + LightClientBootstrap, + LightClientFinalityUpdate, + LightClientHeader, + LightClientOptimisticUpdate, + LightClientUpdate, + phase0, + RootHex, + Slot, + SyncPeriod, +} from "@lodestar/types"; import {createBeaconConfig, BeaconConfig, ChainForkConfig} from "@lodestar/config"; import {isErrorAborted, sleep} from "@lodestar/utils"; import {getCurrentSlot, slotWithFutureTolerance, timeUntilNextEpoch} from "./utils/clock.js"; @@ -32,7 +42,7 @@ export type LightclientInitArgs = { opts?: LightclientOpts; genesisData: GenesisData; transport: LightClientTransport; - bootstrap: allForks.LightClientBootstrap; + bootstrap: LightClientBootstrap; }; /** Provides some protection against a server client sending header updates too far away in the future */ @@ -190,11 +200,11 @@ export class Lightclient { this.updateRunStatus({code: RunStatusCode.stopped}); } - getHead(): allForks.LightClientHeader { + getHead(): LightClientHeader { return this.lightclientSpec.store.optimisticHeader; } - getFinalized(): allForks.LightClientHeader { + getFinalized(): LightClientHeader { return this.lightclientSpec.store.finalizedHeader; } @@ -293,7 +303,7 @@ export class Lightclient { * Processes new optimistic header updates in only known synced sync periods. * This headerUpdate may update the head if there's enough participation. */ - private processOptimisticUpdate(optimisticUpdate: allForks.LightClientOptimisticUpdate): void { + private processOptimisticUpdate(optimisticUpdate: LightClientOptimisticUpdate): void { this.lightclientSpec.onOptimisticUpdate(this.currentSlotWithTolerance(), optimisticUpdate); } @@ -301,11 +311,11 @@ export class Lightclient { * Processes new header updates in only known synced sync periods. * This headerUpdate may update the head if there's enough participation. */ - private processFinalizedUpdate(finalizedUpdate: allForks.LightClientFinalityUpdate): void { + private processFinalizedUpdate(finalizedUpdate: LightClientFinalityUpdate): void { this.lightclientSpec.onFinalityUpdate(this.currentSlotWithTolerance(), finalizedUpdate); } - private processSyncCommitteeUpdate(update: allForks.LightClientUpdate): void { + private processSyncCommitteeUpdate(update: LightClientUpdate): void { this.lightclientSpec.onUpdate(this.currentSlotWithTolerance(), update); } diff --git a/packages/light-client/src/spec/index.ts b/packages/light-client/src/spec/index.ts index e81dba437dea..fc1a431129e8 100644 --- a/packages/light-client/src/spec/index.ts +++ b/packages/light-client/src/spec/index.ts @@ -1,6 +1,12 @@ import {BeaconConfig} from "@lodestar/config"; import {UPDATE_TIMEOUT} from "@lodestar/params"; -import {Slot, allForks} from "@lodestar/types"; +import { + LightClientBootstrap, + LightClientFinalityUpdate, + LightClientOptimisticUpdate, + LightClientUpdate, + Slot, +} from "@lodestar/types"; import {computeSyncPeriodAtSlot} from "../utils/index.js"; import {getSyncCommitteeAtPeriod, processLightClientUpdate, ProcessUpdateOpts} from "./processLightClientUpdate.js"; import {ILightClientStore, LightClientStore, LightClientStoreEvents} from "./store.js"; @@ -17,17 +23,17 @@ export class LightclientSpec { constructor( config: BeaconConfig, private readonly opts: ProcessUpdateOpts & LightClientStoreEvents, - bootstrap: allForks.LightClientBootstrap + bootstrap: LightClientBootstrap ) { this.store = new LightClientStore(config, bootstrap, opts); this.config = config; } - onUpdate(currentSlot: Slot, update: allForks.LightClientUpdate): void { + onUpdate(currentSlot: Slot, update: LightClientUpdate): void { processLightClientUpdate(this.config, this.store, currentSlot, this.opts, update); } - onFinalityUpdate(currentSlot: Slot, finalityUpdate: allForks.LightClientFinalityUpdate): void { + onFinalityUpdate(currentSlot: Slot, finalityUpdate: LightClientFinalityUpdate): void { this.onUpdate(currentSlot, { attestedHeader: finalityUpdate.attestedHeader, nextSyncCommittee: ZERO_SYNC_COMMITTEE, @@ -39,7 +45,7 @@ export class LightclientSpec { }); } - onOptimisticUpdate(currentSlot: Slot, optimisticUpdate: allForks.LightClientOptimisticUpdate): void { + onOptimisticUpdate(currentSlot: Slot, optimisticUpdate: LightClientOptimisticUpdate): void { this.onUpdate(currentSlot, { attestedHeader: optimisticUpdate.attestedHeader, nextSyncCommittee: ZERO_SYNC_COMMITTEE, diff --git a/packages/light-client/src/spec/isBetterUpdate.ts b/packages/light-client/src/spec/isBetterUpdate.ts index e149386cf97f..260d54434fba 100644 --- a/packages/light-client/src/spec/isBetterUpdate.ts +++ b/packages/light-client/src/spec/isBetterUpdate.ts @@ -1,5 +1,5 @@ import {SYNC_COMMITTEE_SIZE} from "@lodestar/params"; -import {Slot, allForks} from "@lodestar/types"; +import {LightClientUpdate, Slot} from "@lodestar/types"; import {computeSyncPeriodAtSlot} from "../utils/index.js"; import {isFinalityUpdate, isSyncCommitteeUpdate, sumBits} from "./utils.js"; @@ -82,7 +82,7 @@ export function isSafeLightClientUpdate(update: LightClientUpdateSummary): boole ); } -export function toLightClientUpdateSummary(update: allForks.LightClientUpdate): LightClientUpdateSummary { +export function toLightClientUpdateSummary(update: LightClientUpdate): LightClientUpdateSummary { return { activeParticipants: sumBits(update.syncAggregate.syncCommitteeBits), attestedHeaderSlot: update.attestedHeader.beacon.slot, diff --git a/packages/light-client/src/spec/processLightClientUpdate.ts b/packages/light-client/src/spec/processLightClientUpdate.ts index acd6a46d0175..644a956ab4b0 100644 --- a/packages/light-client/src/spec/processLightClientUpdate.ts +++ b/packages/light-client/src/spec/processLightClientUpdate.ts @@ -1,5 +1,5 @@ import {SYNC_COMMITTEE_SIZE} from "@lodestar/params"; -import {Slot, SyncPeriod, allForks} from "@lodestar/types"; +import {LightClientUpdate, Slot, SyncPeriod} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {pruneSetToMax} from "@lodestar/utils"; import {computeSyncPeriodAtSlot, deserializeSyncCommittee, sumBits} from "../utils/index.js"; @@ -18,7 +18,7 @@ export function processLightClientUpdate( store: ILightClientStore, currentSlot: Slot, opts: ProcessUpdateOpts, - update: allForks.LightClientUpdate + update: LightClientUpdate ): void { if (update.signatureSlot > currentSlot) { throw Error(`update slot ${update.signatureSlot} must not be in the future, current slot ${currentSlot}`); diff --git a/packages/light-client/src/spec/store.ts b/packages/light-client/src/spec/store.ts index 94c4510d90db..5b606883abcb 100644 --- a/packages/light-client/src/spec/store.ts +++ b/packages/light-client/src/spec/store.ts @@ -1,6 +1,6 @@ import type {PublicKey} from "@chainsafe/bls/types"; import {BeaconConfig} from "@lodestar/config"; -import {SyncPeriod, allForks} from "@lodestar/types"; +import {LightClientBootstrap, LightClientHeader, LightClientUpdate, SyncPeriod} from "@lodestar/types"; import {computeSyncPeriodAtSlot, deserializeSyncCommittee} from "../utils/index.js"; import {LightClientUpdateSummary} from "./isBetterUpdate.js"; @@ -18,29 +18,29 @@ export interface ILightClientStore { setActiveParticipants(period: SyncPeriod, activeParticipants: number): void; // Header that is finalized - finalizedHeader: allForks.LightClientHeader; + finalizedHeader: LightClientHeader; // Most recent available reasonably-safe header - optimisticHeader: allForks.LightClientHeader; + optimisticHeader: LightClientHeader; } export interface LightClientStoreEvents { - onSetFinalizedHeader?: (header: allForks.LightClientHeader) => void; - onSetOptimisticHeader?: (header: allForks.LightClientHeader) => void; + onSetFinalizedHeader?: (header: LightClientHeader) => void; + onSetOptimisticHeader?: (header: LightClientHeader) => void; } export class LightClientStore implements ILightClientStore { readonly syncCommittees = new Map(); readonly bestValidUpdates = new Map(); - private _finalizedHeader: allForks.LightClientHeader; - private _optimisticHeader: allForks.LightClientHeader; + private _finalizedHeader: LightClientHeader; + private _optimisticHeader: LightClientHeader; private readonly maxActiveParticipants = new Map(); constructor( readonly config: BeaconConfig, - bootstrap: allForks.LightClientBootstrap, + bootstrap: LightClientBootstrap, private readonly events: LightClientStoreEvents ) { const bootstrapPeriod = computeSyncPeriodAtSlot(bootstrap.header.beacon.slot); @@ -49,20 +49,20 @@ export class LightClientStore implements ILightClientStore { this._optimisticHeader = bootstrap.header; } - get finalizedHeader(): allForks.LightClientHeader { + get finalizedHeader(): LightClientHeader { return this._finalizedHeader; } - set finalizedHeader(value: allForks.LightClientHeader) { + set finalizedHeader(value: LightClientHeader) { this._finalizedHeader = value; this.events.onSetFinalizedHeader?.(value); } - get optimisticHeader(): allForks.LightClientHeader { + get optimisticHeader(): LightClientHeader { return this._optimisticHeader; } - set optimisticHeader(value: allForks.LightClientHeader) { + set optimisticHeader(value: LightClientHeader) { this._optimisticHeader = value; this.events.onSetOptimisticHeader?.(value); } @@ -95,7 +95,7 @@ export type SyncCommitteeFast = { }; export type LightClientUpdateWithSummary = { - update: allForks.LightClientUpdate; + update: LightClientUpdate; summary: LightClientUpdateSummary; }; diff --git a/packages/light-client/src/spec/utils.ts b/packages/light-client/src/spec/utils.ts index 2a5720a1f637..65d6f3e84c59 100644 --- a/packages/light-client/src/spec/utils.ts +++ b/packages/light-client/src/spec/utils.ts @@ -8,7 +8,16 @@ import { BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH, BLOCK_BODY_EXECUTION_PAYLOAD_INDEX as EXECUTION_PAYLOAD_INDEX, } from "@lodestar/params"; -import {altair, phase0, ssz, allForks, capella, deneb, Slot} from "@lodestar/types"; +import { + ssz, + Slot, + LightClientFinalityUpdate, + LightClientHeader, + LightClientOptimisticUpdate, + LightClientUpdate, + BeaconBlockHeader, + SyncCommittee, +} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {isValidMerkleBranch, computeEpochAtSlot, computeSyncPeriodAtSlot} from "../utils/index.js"; @@ -32,7 +41,7 @@ export function getSafetyThreshold(maxActiveParticipants: number): number { return Math.floor(maxActiveParticipants / SAFETY_THRESHOLD_FACTOR); } -export function isSyncCommitteeUpdate(update: allForks.LightClientUpdate): boolean { +export function isSyncCommitteeUpdate(update: LightClientUpdate): boolean { return ( // Fast return for when constructing full LightClientUpdate from partial updates update.nextSyncCommitteeBranch !== ZERO_NEXT_SYNC_COMMITTEE_BRANCH && @@ -40,7 +49,7 @@ export function isSyncCommitteeUpdate(update: allForks.LightClientUpdate): boole ); } -export function isFinalityUpdate(update: allForks.LightClientUpdate): boolean { +export function isFinalityUpdate(update: LightClientUpdate): boolean { return ( // Fast return for when constructing full LightClientUpdate from partial updates update.finalityBranch !== ZERO_FINALITY_BRANCH && @@ -48,12 +57,12 @@ export function isFinalityUpdate(update: allForks.LightClientUpdate): boolean { ); } -export function isZeroedHeader(header: phase0.BeaconBlockHeader): boolean { +export function isZeroedHeader(header: BeaconBlockHeader): boolean { // Fast return for when constructing full LightClientUpdate from partial updates return header === ZERO_HEADER || byteArrayEquals(header.bodyRoot, ZERO_HASH); } -export function isZeroedSyncCommittee(syncCommittee: altair.SyncCommittee): boolean { +export function isZeroedSyncCommittee(syncCommittee: SyncCommittee): boolean { // Fast return for when constructing full LightClientUpdate from partial updates return syncCommittee === ZERO_SYNC_COMMITTEE || byteArrayEquals(syncCommittee.pubkeys[0], ZERO_PUBKEY); } @@ -61,8 +70,8 @@ export function isZeroedSyncCommittee(syncCommittee: altair.SyncCommittee): bool export function upgradeLightClientHeader( config: ChainForkConfig, targetFork: ForkName, - header: altair.LightClientHeader -): allForks.LightClientHeader { + header: LightClientHeader +): LightClientHeader { const headerFork = config.getForkName(header.beacon.slot); if (ForkSeq[headerFork] >= ForkSeq[targetFork]) { throw Error(`Invalid upgrade request from headerFork=${headerFork} to targetFork=${targetFork}`); @@ -70,7 +79,7 @@ export function upgradeLightClientHeader( // We are modifying the same header object, may be we could create a copy, but its // not required as of now - const upgradedHeader = header as allForks.LightClientHeader; + const upgradedHeader = header; const startUpgradeFromFork = Object.values(ForkName)[ForkSeq[headerFork] + 1]; switch (startUpgradeFromFork) { @@ -86,9 +95,9 @@ export function upgradeLightClientHeader( // eslint-disable-next-line no-fallthrough case ForkName.capella: - (upgradedHeader as capella.LightClientHeader).execution = + (upgradedHeader as LightClientHeader).execution = ssz.capella.LightClientHeader.fields.execution.defaultValue(); - (upgradedHeader as capella.LightClientHeader).executionBranch = + (upgradedHeader as LightClientHeader).executionBranch = ssz.capella.LightClientHeader.fields.executionBranch.defaultValue(); // Break if no further upgradation is required else fall through @@ -96,9 +105,9 @@ export function upgradeLightClientHeader( // eslint-disable-next-line no-fallthrough case ForkName.deneb: - (upgradedHeader as deneb.LightClientHeader).execution.blobGasUsed = + (upgradedHeader as LightClientHeader).execution.blobGasUsed = ssz.deneb.LightClientHeader.fields.execution.fields.blobGasUsed.defaultValue(); - (upgradedHeader as deneb.LightClientHeader).execution.excessBlobGas = + (upgradedHeader as LightClientHeader).execution.excessBlobGas = ssz.deneb.LightClientHeader.fields.execution.fields.excessBlobGas.defaultValue(); // Break if no further upgradation is required else fall through @@ -107,30 +116,30 @@ export function upgradeLightClientHeader( return upgradedHeader; } -export function isValidLightClientHeader(config: ChainForkConfig, header: allForks.LightClientHeader): boolean { +export function isValidLightClientHeader(config: ChainForkConfig, header: LightClientHeader): boolean { const epoch = computeEpochAtSlot(header.beacon.slot); if (epoch < config.CAPELLA_FORK_EPOCH) { return ( - ((header as capella.LightClientHeader).execution === undefined || + ((header as LightClientHeader).execution === undefined || ssz.capella.ExecutionPayloadHeader.equals( - (header as capella.LightClientHeader).execution, + (header as LightClientHeader).execution, ssz.capella.LightClientHeader.fields.execution.defaultValue() )) && - ((header as capella.LightClientHeader).executionBranch === undefined || + ((header as LightClientHeader).executionBranch === undefined || ssz.capella.LightClientHeader.fields.executionBranch.equals( ssz.capella.LightClientHeader.fields.executionBranch.defaultValue(), - (header as capella.LightClientHeader).executionBranch + (header as LightClientHeader).executionBranch )) ); } if (epoch < config.DENEB_FORK_EPOCH) { if ( - ((header as deneb.LightClientHeader).execution.blobGasUsed && - (header as deneb.LightClientHeader).execution.blobGasUsed !== BigInt(0)) || - ((header as deneb.LightClientHeader).execution.excessBlobGas && - (header as deneb.LightClientHeader).execution.excessBlobGas !== BigInt(0)) + ((header as LightClientHeader).execution.blobGasUsed && + (header as LightClientHeader).execution.blobGasUsed !== BigInt(0)) || + ((header as LightClientHeader).execution.excessBlobGas && + (header as LightClientHeader).execution.excessBlobGas !== BigInt(0)) ) { return false; } @@ -139,8 +148,8 @@ export function isValidLightClientHeader(config: ChainForkConfig, header: allFor return isValidMerkleBranch( config .getExecutionForkTypes(header.beacon.slot) - .ExecutionPayloadHeader.hashTreeRoot((header as capella.LightClientHeader).execution), - (header as capella.LightClientHeader).executionBranch, + .ExecutionPayloadHeader.hashTreeRoot((header as LightClientHeader).execution), + (header as LightClientHeader).executionBranch, EXECUTION_PAYLOAD_DEPTH, EXECUTION_PAYLOAD_INDEX, header.beacon.bodyRoot @@ -150,8 +159,8 @@ export function isValidLightClientHeader(config: ChainForkConfig, header: allFor export function upgradeLightClientUpdate( config: ChainForkConfig, targetFork: ForkName, - update: allForks.LightClientUpdate -): allForks.LightClientUpdate { + update: LightClientUpdate +): LightClientUpdate { update.attestedHeader = upgradeLightClientHeader(config, targetFork, update.attestedHeader); update.finalizedHeader = upgradeLightClientHeader(config, targetFork, update.finalizedHeader); @@ -161,8 +170,8 @@ export function upgradeLightClientUpdate( export function upgradeLightClientFinalityUpdate( config: ChainForkConfig, targetFork: ForkName, - finalityUpdate: allForks.LightClientFinalityUpdate -): allForks.LightClientFinalityUpdate { + finalityUpdate: LightClientFinalityUpdate +): LightClientFinalityUpdate { finalityUpdate.attestedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.attestedHeader); finalityUpdate.finalizedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.finalizedHeader); @@ -172,8 +181,8 @@ export function upgradeLightClientFinalityUpdate( export function upgradeLightClientOptimisticUpdate( config: ChainForkConfig, targetFork: ForkName, - optimisticUpdate: allForks.LightClientOptimisticUpdate -): allForks.LightClientOptimisticUpdate { + optimisticUpdate: LightClientOptimisticUpdate +): LightClientOptimisticUpdate { optimisticUpdate.attestedHeader = upgradeLightClientHeader(config, targetFork, optimisticUpdate.attestedHeader); return optimisticUpdate; diff --git a/packages/light-client/src/spec/validateLightClientBootstrap.ts b/packages/light-client/src/spec/validateLightClientBootstrap.ts index ab3660ee87ec..30540da24bd1 100644 --- a/packages/light-client/src/spec/validateLightClientBootstrap.ts +++ b/packages/light-client/src/spec/validateLightClientBootstrap.ts @@ -1,5 +1,5 @@ import {byteArrayEquals} from "@chainsafe/ssz"; -import {Root, ssz, allForks} from "@lodestar/types"; +import {LightClientBootstrap, Root, ssz} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {toHex} from "@lodestar/utils"; import {isValidMerkleBranch} from "../utils/verifyMerkleBranch.js"; @@ -11,7 +11,7 @@ const CURRENT_SYNC_COMMITTEE_DEPTH = 5; export function validateLightClientBootstrap( config: ChainForkConfig, trustedBlockRoot: Root, - bootstrap: allForks.LightClientBootstrap + bootstrap: LightClientBootstrap ): void { const headerRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(bootstrap.header.beacon); diff --git a/packages/light-client/src/spec/validateLightClientUpdate.ts b/packages/light-client/src/spec/validateLightClientUpdate.ts index 2629986e85f2..fde760da3b05 100644 --- a/packages/light-client/src/spec/validateLightClientUpdate.ts +++ b/packages/light-client/src/spec/validateLightClientUpdate.ts @@ -1,6 +1,6 @@ import bls from "@chainsafe/bls"; import type {PublicKey, Signature} from "@chainsafe/bls/types"; -import {Root, ssz, allForks} from "@lodestar/types"; +import {LightClientUpdate, Root, ssz} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import { FINALIZED_ROOT_INDEX, @@ -27,7 +27,7 @@ import {ILightClientStore} from "./store.js"; export function validateLightClientUpdate( config: ChainForkConfig, store: ILightClientStore, - update: allForks.LightClientUpdate, + update: LightClientUpdate, syncCommittee: SyncCommitteeFast ): void { // Verify sync committee has sufficient participants diff --git a/packages/light-client/src/transport/interface.ts b/packages/light-client/src/transport/interface.ts index dcd3210df70c..dc9c697c00be 100644 --- a/packages/light-client/src/transport/interface.ts +++ b/packages/light-client/src/transport/interface.ts @@ -1,4 +1,10 @@ -import {allForks, SyncPeriod} from "@lodestar/types"; +import { + LightClientBootstrap, + LightClientFinalityUpdate, + LightClientOptimisticUpdate, + LightClientUpdate, + SyncPeriod, +} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; export interface LightClientTransport { @@ -8,24 +14,24 @@ export interface LightClientTransport { ): Promise< { version: ForkName; - data: allForks.LightClientUpdate; + data: LightClientUpdate; }[] >; /** * Returns the latest optimistic head update available. Clients should use the SSE type `light_client_optimistic_update` * unless to get the very first head update after syncing, or if SSE are not supported by the server. */ - getOptimisticUpdate(): Promise<{version: ForkName; data: allForks.LightClientOptimisticUpdate}>; - getFinalityUpdate(): Promise<{version: ForkName; data: allForks.LightClientFinalityUpdate}>; + getOptimisticUpdate(): Promise<{version: ForkName; data: LightClientOptimisticUpdate}>; + getFinalityUpdate(): Promise<{version: ForkName; data: LightClientFinalityUpdate}>; /** * Fetch a bootstrapping state with a proof to a trusted block root. * The trusted block root should be fetched with similar means to a weak subjectivity checkpoint. * Only block roots for checkpoints are guaranteed to be available. */ - getBootstrap(blockRoot: string): Promise<{version: ForkName; data: allForks.LightClientBootstrap}>; + getBootstrap(blockRoot: string): Promise<{version: ForkName; data: LightClientBootstrap}>; // registers handler for LightClientOptimisticUpdate. This can come either via sse or p2p - onOptimisticUpdate(handler: (optimisticUpdate: allForks.LightClientOptimisticUpdate) => void): void; + onOptimisticUpdate(handler: (optimisticUpdate: LightClientOptimisticUpdate) => void): void; // registers handler for LightClientFinalityUpdate. This can come either via sse or p2p - onFinalityUpdate(handler: (finalityUpdate: allForks.LightClientFinalityUpdate) => void): void; + onFinalityUpdate(handler: (finalityUpdate: LightClientFinalityUpdate) => void): void; } diff --git a/packages/light-client/src/transport/rest.ts b/packages/light-client/src/transport/rest.ts index bc19594e3930..c260e1aaeee3 100644 --- a/packages/light-client/src/transport/rest.ts +++ b/packages/light-client/src/transport/rest.ts @@ -1,13 +1,19 @@ import mitt from "mitt"; -import {type allForks, type SyncPeriod} from "@lodestar/types"; +import { + LightClientBootstrap, + LightClientFinalityUpdate, + LightClientOptimisticUpdate, + LightClientUpdate, + type SyncPeriod, +} from "@lodestar/types"; import {type ApiClient, routes} from "@lodestar/api"; import {type ForkName} from "@lodestar/params"; import {MittEmitter} from "../events.js"; import {type LightClientTransport} from "./interface.js"; export type LightClientRestEvents = { - [routes.events.EventType.lightClientFinalityUpdate]: (update: allForks.LightClientFinalityUpdate) => void; - [routes.events.EventType.lightClientOptimisticUpdate]: (update: allForks.LightClientOptimisticUpdate) => void; + [routes.events.EventType.lightClientFinalityUpdate]: (update: LightClientFinalityUpdate) => void; + [routes.events.EventType.lightClientOptimisticUpdate]: (update: LightClientOptimisticUpdate) => void; }; export type LightClientRestEmitter = MittEmitter; @@ -25,7 +31,7 @@ export class LightClientRestTransport implements LightClientTransport { ): Promise< { version: ForkName; - data: allForks.LightClientUpdate; + data: LightClientUpdate; }[] > { const res = await this.api.lightclient.getLightClientUpdatesByRange({startPeriod, count}); @@ -34,27 +40,27 @@ export class LightClientRestTransport implements LightClientTransport { return updates.map((data, i) => ({data, version: versions[i]})); } - async getOptimisticUpdate(): Promise<{version: ForkName; data: allForks.LightClientOptimisticUpdate}> { + async getOptimisticUpdate(): Promise<{version: ForkName; data: LightClientOptimisticUpdate}> { const res = await this.api.lightclient.getLightClientOptimisticUpdate(); return {version: res.meta().version, data: res.value()}; } - async getFinalityUpdate(): Promise<{version: ForkName; data: allForks.LightClientFinalityUpdate}> { + async getFinalityUpdate(): Promise<{version: ForkName; data: LightClientFinalityUpdate}> { const res = await this.api.lightclient.getLightClientFinalityUpdate(); return {version: res.meta().version, data: res.value()}; } - async getBootstrap(blockRoot: string): Promise<{version: ForkName; data: allForks.LightClientBootstrap}> { + async getBootstrap(blockRoot: string): Promise<{version: ForkName; data: LightClientBootstrap}> { const res = await this.api.lightclient.getLightClientBootstrap({blockRoot}); return {version: res.meta().version, data: res.value()}; } - onOptimisticUpdate(handler: (optimisticUpdate: allForks.LightClientOptimisticUpdate) => void): void { + onOptimisticUpdate(handler: (optimisticUpdate: LightClientOptimisticUpdate) => void): void { this.subscribeEventstream(); this.eventEmitter.on(routes.events.EventType.lightClientOptimisticUpdate, handler); } - onFinalityUpdate(handler: (finalityUpdate: allForks.LightClientFinalityUpdate) => void): void { + onFinalityUpdate(handler: (finalityUpdate: LightClientFinalityUpdate) => void): void { this.subscribeEventstream(); this.eventEmitter.on(routes.events.EventType.lightClientFinalityUpdate, handler); } diff --git a/packages/light-client/src/types.ts b/packages/light-client/src/types.ts index 6e6126f8a481..518edd349c63 100644 --- a/packages/light-client/src/types.ts +++ b/packages/light-client/src/types.ts @@ -1,14 +1,14 @@ import type {PublicKey} from "@chainsafe/bls/types"; -import {SyncPeriod, allForks} from "@lodestar/types"; +import {LightClientHeader, LightClientUpdate, SyncPeriod} from "@lodestar/types"; export type LightClientStoreFast = { snapshot: LightClientSnapshotFast; - bestUpdates: Map; + bestUpdates: Map; }; export type LightClientSnapshotFast = { /** Beacon block header */ - header: allForks.LightClientHeader; + header: LightClientHeader; /** Sync committees corresponding to the header */ currentSyncCommittee: SyncCommitteeFast; nextSyncCommittee: SyncCommitteeFast; diff --git a/packages/light-client/src/validation.ts b/packages/light-client/src/validation.ts index 85c5c35a2cea..e7839f115153 100644 --- a/packages/light-client/src/validation.ts +++ b/packages/light-client/src/validation.ts @@ -1,6 +1,6 @@ import bls from "@chainsafe/bls"; import type {PublicKey, Signature} from "@chainsafe/bls/types"; -import {altair, Root, Slot, ssz, allForks} from "@lodestar/types"; +import {altair, LightClientFinalityUpdate, LightClientUpdate, Root, Slot, ssz} from "@lodestar/types"; import { FINALIZED_ROOT_INDEX, FINALIZED_ROOT_DEPTH, @@ -24,7 +24,7 @@ import {computeSyncPeriodAtSlot} from "./utils/clock.js"; export function assertValidLightClientUpdate( config: BeaconConfig, syncCommittee: SyncCommitteeFast, - update: allForks.LightClientUpdate + update: LightClientUpdate ): void { // DIFF FROM SPEC: An update with the same header.slot can be valid and valuable to the lightclient // It may have more consensus and result in a better snapshot whilst not advancing the state @@ -64,7 +64,7 @@ export function assertValidLightClientUpdate( * * Where `hashTreeRoot(state) == update.finalityHeader.stateRoot` */ -export function assertValidFinalityProof(update: allForks.LightClientFinalityUpdate): void { +export function assertValidFinalityProof(update: LightClientFinalityUpdate): void { if ( !isValidMerkleBranch( ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.finalizedHeader.beacon), @@ -94,7 +94,7 @@ export function assertValidFinalityProof(update: allForks.LightClientFinalityUpd * * Where `hashTreeRoot(state) == update.header.stateRoot` */ -export function assertValidSyncCommitteeProof(update: allForks.LightClientUpdate): void { +export function assertValidSyncCommitteeProof(update: LightClientUpdate): void { if ( !isValidMerkleBranch( ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee), diff --git a/packages/light-client/test/unit/isValidLightClientHeader.test.ts b/packages/light-client/test/unit/isValidLightClientHeader.test.ts index 40efa1293231..db27a8266103 100644 --- a/packages/light-client/test/unit/isValidLightClientHeader.test.ts +++ b/packages/light-client/test/unit/isValidLightClientHeader.test.ts @@ -1,6 +1,6 @@ import {describe, it, expect} from "vitest"; import {fromHexString} from "@chainsafe/ssz"; -import {ssz, allForks} from "@lodestar/types"; +import {LightClientHeader, ssz} from "@lodestar/types"; import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {isValidLightClientHeader} from "../../src/spec/utils.js"; @@ -80,7 +80,7 @@ describe("isValidLightClientHeader", function () { executionBranch: capellaLCHeader.executionBranch, }; - const testCases: [string, allForks.LightClientHeader][] = [ + const testCases: [string, LightClientHeader][] = [ ["altair LC header", altairLCHeader], ["altair upgraded to capella", altairUpgradedCapellaLCHeader], ["altair upgraded to deneb", altairUpgradedDenebLCHeader], @@ -88,7 +88,7 @@ describe("isValidLightClientHeader", function () { ["capella upgraded to deneb LC header", capellaUpgradedDenebHeader], ]; - testCases.forEach(([name, header]: [string, allForks.LightClientHeader]) => { + testCases.forEach(([name, header]: [string, LightClientHeader]) => { it(name, function () { const isValid = isValidLightClientHeader(config, header); expect(isValid).toBe(true); diff --git a/packages/light-client/test/utils/utils.ts b/packages/light-client/test/utils/utils.ts index a99004345217..8364bcc7fc85 100644 --- a/packages/light-client/test/utils/utils.ts +++ b/packages/light-client/test/utils/utils.ts @@ -11,7 +11,7 @@ import { SLOTS_PER_EPOCH, SYNC_COMMITTEE_SIZE, } from "@lodestar/params"; -import {altair, phase0, Slot, ssz, SyncPeriod, allForks} from "@lodestar/types"; +import {altair, LightClientBootstrap, phase0, Slot, ssz, SyncPeriod} from "@lodestar/types"; import {SyncCommitteeFast} from "../../src/types.js"; import {computeSigningRoot} from "../../src/utils/domain.js"; import {getConsoleLogger} from "../../src/utils/logger.js"; @@ -156,7 +156,7 @@ export function computeLightclientUpdate(config: BeaconConfig, period: SyncPerio * Creates a LightClientBootstrap that passes validation */ export function computeLightClientSnapshot(period: SyncPeriod): { - snapshot: allForks.LightClientBootstrap; + snapshot: LightClientBootstrap; checkpointRoot: Uint8Array; } { const currentSyncCommittee = getInteropSyncCommittee(period).syncCommittee; diff --git a/packages/params/src/forkName.ts b/packages/params/src/forkName.ts index 142684c313f4..fa3be24bfae4 100644 --- a/packages/params/src/forkName.ts +++ b/packages/params/src/forkName.ts @@ -20,6 +20,8 @@ export enum ForkSeq { deneb = 4, } +export type ForkAll = ForkName; + export type ForkPreLightClient = ForkName.phase0; export type ForkLightClient = Exclude; export function isForkLightClient(fork: ForkName): fork is ForkLightClient { diff --git a/packages/prover/src/proof_provider/payload_store.ts b/packages/prover/src/proof_provider/payload_store.ts index 30d5b2126296..343ec63719f9 100644 --- a/packages/prover/src/proof_provider/payload_store.ts +++ b/packages/prover/src/proof_provider/payload_store.ts @@ -1,6 +1,7 @@ import {ApiClient} from "@lodestar/api"; -import {allForks, capella} from "@lodestar/types"; import {Logger} from "@lodestar/utils"; +import {ExecutionPayload, LightClientHeader} from "@lodestar/types"; +import {ForkName} from "@lodestar/params"; import {MAX_PAYLOAD_HISTORY} from "../constants.js"; import {fetchBlock, getExecutionPayloadForBlockNumber} from "../utils/consensus.js"; import {bufferToHex, hexToNumber} from "../utils/conversion.js"; @@ -28,13 +29,13 @@ export class PayloadStore { private unfinalizedRoots = new Map(); // Payloads store with BlockELRoot as key - private payloads = new Map(); + private payloads = new Map(); private latestBlockRoot: BlockELRoot | null = null; constructor(private opts: {api: ApiClient; logger: Logger}) {} - get finalized(): allForks.ExecutionPayload | undefined { + get finalized(): ExecutionPayload | undefined { const maxBlockNumberForFinalized = this.finalizedRoots.max; if (maxBlockNumberForFinalized === undefined) { @@ -49,7 +50,7 @@ export class PayloadStore { return undefined; } - get latest(): allForks.ExecutionPayload | undefined { + get latest(): ExecutionPayload | undefined { if (this.latestBlockRoot) { return this.payloads.get(this.latestBlockRoot); } @@ -57,7 +58,7 @@ export class PayloadStore { return undefined; } - async get(blockId: number | string): Promise { + async get(blockId: number | string): Promise { // Given block id is a block hash in hex (32 bytes root takes 64 hex chars + 2 for 0x prefix) if (typeof blockId === "string" && blockId.startsWith("0x") && blockId.length === 64 + 2) { return this.payloads.get(blockId); @@ -81,7 +82,7 @@ export class PayloadStore { return undefined; } - protected async getOrFetchFinalizedPayload(blockNumber: number): Promise { + protected async getOrFetchFinalizedPayload(blockNumber: number): Promise { const maxBlockNumberForFinalized = this.finalizedRoots.max; const minBlockNumberForFinalized = this.finalizedRoots.min; @@ -116,7 +117,7 @@ export class PayloadStore { return undefined; } - set(payload: allForks.ExecutionPayload, slot: number, finalized: boolean): void { + set(payload: ExecutionPayload, slot: number, finalized: boolean): void { const blockELRoot = bufferToHex(payload.blockHash); this.payloads.set(blockELRoot, payload); @@ -134,7 +135,7 @@ export class PayloadStore { } } - async processLCHeader(header: capella.LightClientHeader, finalized = false): Promise { + async processLCHeader(header: LightClientHeader, finalized = false): Promise { const blockSlot = header.beacon.slot; const blockNumber = header.execution.blockNumber; const blockELRoot = bufferToHex(header.execution.blockHash); diff --git a/packages/prover/src/proof_provider/proof_provider.ts b/packages/prover/src/proof_provider/proof_provider.ts index 4cced315da04..3a95b0e4e604 100644 --- a/packages/prover/src/proof_provider/proof_provider.ts +++ b/packages/prover/src/proof_provider/proof_provider.ts @@ -3,9 +3,9 @@ import {ChainForkConfig, createChainForkConfig} from "@lodestar/config"; import {NetworkName, networksChainConfig} from "@lodestar/config/networks"; import {Lightclient, LightclientEvent, RunStatusCode} from "@lodestar/light-client"; import {LightClientRestTransport} from "@lodestar/light-client/transport"; -import {isForkWithdrawals} from "@lodestar/params"; -import {allForks, capella} from "@lodestar/types"; +import {ForkName, isForkWithdrawals} from "@lodestar/params"; import {Logger} from "@lodestar/utils"; +import {ExecutionPayload, LightClientHeader} from "@lodestar/types"; import {LCTransport, RootProviderInitOptions} from "../interfaces.js"; import {assertLightClient} from "../utils/assertion.js"; import { @@ -146,7 +146,7 @@ export class ProofProvider { }; } - async getExecutionPayload(blockNumber: number | string | "finalized" | "latest"): Promise { + async getExecutionPayload(blockNumber: number | string | "finalized" | "latest"): Promise { assertLightClient(this.lightClient); if (typeof blockNumber === "string" && blockNumber === "finalized") { @@ -170,7 +170,7 @@ export class ProofProvider { throw new Error(`Invalid blockNumber "${blockNumber}"`); } - async processLCHeader(lcHeader: allForks.LightClientHeader, finalized = false): Promise { + async processLCHeader(lcHeader: LightClientHeader, finalized = false): Promise { const fork = this.opts.config.getForkName(lcHeader.beacon.slot); if (!isForkWithdrawals(fork)) { @@ -185,7 +185,7 @@ export class ProofProvider { throw new Error("Execution payload is required for execution fork"); } - await this.store.processLCHeader(lcHeader as capella.LightClientHeader, finalized); + await this.store.processLCHeader(lcHeader as LightClientHeader, finalized); } private registerEvents(): void { diff --git a/packages/prover/src/utils/consensus.ts b/packages/prover/src/utils/consensus.ts index c34558e96e6d..ac8fd24d139f 100644 --- a/packages/prover/src/utils/consensus.ts +++ b/packages/prover/src/utils/consensus.ts @@ -1,5 +1,5 @@ import {ApiClient} from "@lodestar/api/beacon"; -import {allForks, Bytes32, capella} from "@lodestar/types"; +import {Bytes32, ExecutionPayload, capella} from "@lodestar/types"; import {GenesisData, Lightclient} from "@lodestar/light-client"; import {Logger} from "@lodestar/utils"; import {MAX_PAYLOAD_HISTORY} from "../constants.js"; @@ -49,7 +49,7 @@ export async function getExecutionPayloads({ startSlot: number; endSlot: number; logger: Logger; -}): Promise> { +}): Promise> { [startSlot, endSlot] = [Math.min(startSlot, endSlot), Math.max(startSlot, endSlot)]; if (startSlot === endSlot) { logger.debug("Fetching EL payload", {slot: startSlot}); @@ -57,7 +57,7 @@ export async function getExecutionPayloads({ logger.debug("Fetching EL payloads", {startSlot, endSlot}); } - const payloads = new Map(); + const payloads = new Map(); let slot = endSlot; let block = await fetchNearestBlock(api, slot); @@ -82,8 +82,8 @@ export async function getExecutionPayloadForBlockNumber( api: ApiClient, startSlot: number, blockNumber: number -): Promise> { - const payloads = new Map(); +): Promise> { + const payloads = new Map(); let block = await fetchNearestBlock(api, startSlot); payloads.set(block.message.slot, block.message.body.executionPayload); diff --git a/packages/prover/src/utils/evm.ts b/packages/prover/src/utils/evm.ts index bbdba907c35f..19f43d9584c8 100644 --- a/packages/prover/src/utils/evm.ts +++ b/packages/prover/src/utils/evm.ts @@ -4,8 +4,8 @@ import {VM, RunTxResult} from "@ethereumjs/vm"; import {TransactionFactory} from "@ethereumjs/tx"; import {Block, BlockHeader} from "@ethereumjs/block"; import {NetworkName} from "@lodestar/config/networks"; -import {allForks} from "@lodestar/types"; import {Logger} from "@lodestar/utils"; +import {ExecutionPayload} from "@lodestar/types"; import {ZERO_ADDRESS} from "../constants.js"; import {ProofProvider} from "../proof_provider/proof_provider.js"; import {ELBlock, ELProof, ELTransaction, JsonRpcVersion} from "../types.js"; @@ -41,7 +41,7 @@ export async function getVMWithState({ }: { rpc: ELRpcProvider; vm: VM; - executionPayload: allForks.ExecutionPayload; + executionPayload: ExecutionPayload; tx: ELTransaction; logger: Logger; }): Promise { @@ -163,7 +163,7 @@ export async function executeVMCall({ rpc: ELRpcProvider; tx: ELTransaction; vm: VM; - executionPayload: allForks.ExecutionPayload; + executionPayload: ExecutionPayload; network: NetworkName; }): Promise { const {from, to, gas, gasPrice, maxPriorityFeePerGas, value, data, input} = tx; @@ -205,7 +205,7 @@ export async function executeVMTx({ rpc: ELRpcProvider; tx: ELTransaction; vm: VM; - executionPayload: allForks.ExecutionPayload; + executionPayload: ExecutionPayload; network: NetworkName; }): Promise { const {result: block} = await rpc.request("eth_getBlockByHash", [bufferToHex(executionPayload.blockHash), true], { @@ -258,7 +258,7 @@ export async function executeVMTx({ export function getVMBlockHeaderFromELBlock( block: ELBlock, - executionPayload: allForks.ExecutionPayload, + executionPayload: ExecutionPayload, network: NetworkName ): BlockHeader { const blockHeaderData = { diff --git a/packages/prover/src/utils/validation.ts b/packages/prover/src/utils/validation.ts index 1615df1db9b4..3adc37571db5 100644 --- a/packages/prover/src/utils/validation.ts +++ b/packages/prover/src/utils/validation.ts @@ -3,7 +3,7 @@ import {RLP} from "@ethereumjs/rlp"; import {Trie} from "@ethereumjs/trie"; import {Account, KECCAK256_NULL_S} from "@ethereumjs/util"; import {keccak256} from "ethereum-cryptography/keccak.js"; -import {Bytes32, allForks} from "@lodestar/types"; +import {Bytes32, ExecutionPayload} from "@lodestar/types"; import {Logger} from "@lodestar/utils"; import {ChainForkConfig} from "@lodestar/config"; import {ELBlock, ELProof, ELStorageProof, HexString} from "../types.js"; @@ -99,7 +99,7 @@ export async function isValidBlock({ logger, config, }: { - executionPayload: allForks.ExecutionPayload; + executionPayload: ExecutionPayload; block: ELBlock; logger: Logger; config: ChainForkConfig; diff --git a/packages/prover/test/unit/proof_provider/payload_store.test.ts b/packages/prover/test/unit/proof_provider/payload_store.test.ts index 81b8ffe3c16c..b482bb579e77 100644 --- a/packages/prover/test/unit/proof_provider/payload_store.test.ts +++ b/packages/prover/test/unit/proof_provider/payload_store.test.ts @@ -3,7 +3,7 @@ import {when} from "vitest-when"; import {ApiClient, ApiResponse, HttpStatusCode, routes} from "@lodestar/api"; import {hash} from "@lodestar/utils"; import {Logger} from "@lodestar/logger"; -import {allForks, capella} from "@lodestar/types"; +import {ExecutionPayload, SignedBeaconBlock, capella} from "@lodestar/types"; import {toHexString} from "@lodestar/utils"; import {ForkName} from "@lodestar/params"; import {PayloadStore} from "../../../src/proof_provider/payload_store.js"; @@ -12,12 +12,12 @@ import {MAX_PAYLOAD_HISTORY} from "../../../src/constants.js"; const slotNumber = 10; const createHash = (input: string): Uint8Array => hash(Buffer.from(input, "utf8")); -const buildPayload = ({blockNumber}: {blockNumber: number}): allForks.ExecutionPayload => +const buildPayload = ({blockNumber}: {blockNumber: number}): ExecutionPayload => ({ blockNumber, blockHash: createHash(`"block-hash-${blockNumber}`), parentHash: createHash(`"parent-hash-${blockNumber}`), - }) as unknown as allForks.ExecutionPayload; + }) as unknown as ExecutionPayload; const buildLCHeader = ({slot, blockNumber}: {slot: number; blockNumber: number}): capella.LightClientHeader => ({ @@ -25,7 +25,7 @@ const buildLCHeader = ({slot, blockNumber}: {slot: number; blockNumber: number}) execution: buildPayload({blockNumber}), }) as unknown as capella.LightClientHeader; -const buildBlock = ({slot, blockNumber}: {slot: number; blockNumber: number}): allForks.SignedBeaconBlock => +const buildBlock = ({slot, blockNumber}: {slot: number; blockNumber: number}): SignedBeaconBlock => ({ signature: createHash(`"beacon-block-signature-${slot}`), message: { @@ -37,7 +37,7 @@ const buildBlock = ({slot, blockNumber}: {slot: number; blockNumber: number}): a executionPayload: buildPayload({blockNumber}), }, }, - }) as unknown as allForks.SignedBeaconBlock; + }) as unknown as SignedBeaconBlock; const buildBlockResponse = ({ slot, diff --git a/packages/state-transition/src/block/index.ts b/packages/state-transition/src/block/index.ts index b235f7ca24ef..fdfc9e903518 100644 --- a/packages/state-transition/src/block/index.ts +++ b/packages/state-transition/src/block/index.ts @@ -1,5 +1,5 @@ import {ForkSeq} from "@lodestar/params"; -import {allForks, altair, capella} from "@lodestar/types"; +import {BeaconBlock, BlindedBeaconBlock, altair, capella} from "@lodestar/types"; import {getFullOrBlindedPayload, isExecutionEnabled} from "../util/execution.js"; import {CachedBeaconStateAllForks, CachedBeaconStateCapella, CachedBeaconStateBellatrix} from "../types.js"; import {processExecutionPayload} from "./processExecutionPayload.js"; @@ -31,7 +31,7 @@ export * from "./externalData.js"; export function processBlock( fork: ForkSeq, state: CachedBeaconStateAllForks, - block: allForks.FullOrBlindedBeaconBlock, + block: BeaconBlock | BlindedBeaconBlock, externalData: BlockExternalData & ProcessBlockOpts, opts?: ProcessBlockOpts ): void { diff --git a/packages/state-transition/src/block/processBlockHeader.ts b/packages/state-transition/src/block/processBlockHeader.ts index e37372e7b3ac..755850969b4f 100644 --- a/packages/state-transition/src/block/processBlockHeader.ts +++ b/packages/state-transition/src/block/processBlockHeader.ts @@ -1,5 +1,5 @@ import {toHexString, byteArrayEquals} from "@chainsafe/ssz"; -import {allForks, ssz} from "@lodestar/types"; +import {BeaconBlock, BlindedBeaconBlock, ssz} from "@lodestar/types"; import {CachedBeaconStateAllForks} from "../types.js"; import {ZERO_HASH} from "../constants/index.js"; import {blindedOrFullBlockToHeader} from "../util/index.js"; @@ -9,7 +9,7 @@ import {blindedOrFullBlockToHeader} from "../util/index.js"; * PERF: Fixed work independent of block contents. * NOTE: `block` body root MUST be pre-cached. */ -export function processBlockHeader(state: CachedBeaconStateAllForks, block: allForks.FullOrBlindedBeaconBlock): void { +export function processBlockHeader(state: CachedBeaconStateAllForks, block: BeaconBlock | BlindedBeaconBlock): void { const slot = state.slot; // verify that the slots match if (block.slot !== slot) { diff --git a/packages/state-transition/src/block/processExecutionPayload.ts b/packages/state-transition/src/block/processExecutionPayload.ts index b589436012a5..3c28a400d3bf 100644 --- a/packages/state-transition/src/block/processExecutionPayload.ts +++ b/packages/state-transition/src/block/processExecutionPayload.ts @@ -1,10 +1,9 @@ import {toHexString, byteArrayEquals} from "@chainsafe/ssz"; -import {allForks, deneb} from "@lodestar/types"; +import {BeaconBlockBody, BlindedBeaconBlockBody, deneb, isExecutionPayload} from "@lodestar/types"; import {ForkSeq, MAX_BLOBS_PER_BLOCK} from "@lodestar/params"; import {CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js"; import {getRandaoMix} from "../util/index.js"; import { - isExecutionPayload, isMergeTransitionComplete, getFullOrBlindedPayloadFromBody, executionPayloadToPayloadHeader, @@ -14,7 +13,7 @@ import {BlockExternalData, ExecutionPayloadStatus} from "./externalData.js"; export function processExecutionPayload( fork: ForkSeq, state: CachedBeaconStateBellatrix | CachedBeaconStateCapella, - body: allForks.FullOrBlindedBeaconBlockBody, + body: BeaconBlockBody | BlindedBeaconBlockBody, externalData: Omit ): void { const payload = getFullOrBlindedPayloadFromBody(body); @@ -76,8 +75,8 @@ export function processExecutionPayload( const payloadHeader = isExecutionPayload(payload) ? executionPayloadToPayloadHeader(fork, payload) : payload; - // TODO Deneb: Types are not happy by default. Since it's a generic allForks type going through ViewDU - // transformation then into allForks, probably some weird intersection incompatibility happens + // TODO Deneb: Types are not happy by default. Since it's a generic type going through ViewDU + // transformation then into all forks compatible probably some weird intersection incompatibility happens state.latestExecutionPayloadHeader = state.config .getExecutionForkTypes(state.slot) .ExecutionPayloadHeader.toViewDU(payloadHeader) as typeof state.latestExecutionPayloadHeader; diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index c523f1dad246..38716bb42a40 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -1,4 +1,4 @@ -import {allForks, capella} from "@lodestar/types"; +import {BeaconBlockBody, capella} from "@lodestar/types"; import {ForkSeq, MAX_DEPOSITS} from "@lodestar/params"; import {CachedBeaconStateAllForks, CachedBeaconStateCapella} from "../types.js"; @@ -22,7 +22,7 @@ export { export function processOperations( fork: ForkSeq, state: CachedBeaconStateAllForks, - body: allForks.BeaconBlockBody, + body: BeaconBlockBody, opts: ProcessBlockOpts = {verifySignatures: true} ): void { // verify that outstanding deposits are processed up to the maximum number of deposits diff --git a/packages/state-transition/src/block/processRandao.ts b/packages/state-transition/src/block/processRandao.ts index effeb34390cb..65bcd60f52f6 100644 --- a/packages/state-transition/src/block/processRandao.ts +++ b/packages/state-transition/src/block/processRandao.ts @@ -1,5 +1,5 @@ import {digest} from "@chainsafe/as-sha256"; -import {allForks} from "@lodestar/types"; +import {BeaconBlock} from "@lodestar/types"; import {EPOCHS_PER_HISTORICAL_VECTOR} from "@lodestar/params"; import {getRandaoMix} from "../util/index.js"; import {verifyRandaoSignature} from "../signatureSets/index.js"; @@ -10,11 +10,7 @@ import {CachedBeaconStateAllForks} from "../types.js"; * * PERF: Fixed work independent of block contents. */ -export function processRandao( - state: CachedBeaconStateAllForks, - block: allForks.BeaconBlock, - verifySignature = true -): void { +export function processRandao(state: CachedBeaconStateAllForks, block: BeaconBlock, verifySignature = true): void { const {epochCtx} = state; const epoch = epochCtx.epoch; const randaoReveal = block.body.randaoReveal; diff --git a/packages/state-transition/src/cache/types.ts b/packages/state-transition/src/cache/types.ts index 39b1dbb4b45b..d6d8a3c37904 100644 --- a/packages/state-transition/src/cache/types.ts +++ b/packages/state-transition/src/cache/types.ts @@ -1,25 +1,15 @@ import {CompositeViewDU} from "@chainsafe/ssz"; -import {Epoch, RootHex, ssz} from "@lodestar/types"; +import {Epoch, RootHex, SSZTypesFor} from "@lodestar/types"; +import {ForkAll, ForkExecution, ForkName} from "@lodestar/params"; import {EpochShuffling} from "../util/epochShuffling.js"; -export type BeaconStatePhase0 = CompositeViewDU; -export type BeaconStateAltair = CompositeViewDU; -export type BeaconStateBellatrix = CompositeViewDU; -export type BeaconStateCapella = CompositeViewDU; -export type BeaconStateDeneb = CompositeViewDU; +export type BeaconStatePhase0 = CompositeViewDU>; +export type BeaconStateAltair = CompositeViewDU>; +export type BeaconStateBellatrix = CompositeViewDU>; +export type BeaconStateCapella = CompositeViewDU>; +export type BeaconStateDeneb = CompositeViewDU>; -// Union at the TreeViewDU level -// - Works well as function argument and as generic type for allForks functions -// -// Quasy equivalent to -// CompositeViewDU // + future forks -export type BeaconStateAllForks = - | BeaconStatePhase0 - | BeaconStateAltair - | BeaconStateBellatrix - | BeaconStateCapella - | BeaconStateDeneb; - -export type BeaconStateExecutions = BeaconStateBellatrix | BeaconStateCapella | BeaconStateDeneb; +export type BeaconStateAllForks = CompositeViewDU>; +export type BeaconStateExecutions = CompositeViewDU>; export type ShufflingGetter = (shufflingEpoch: Epoch, dependentRoot: RootHex) => EpochShuffling | null; diff --git a/packages/state-transition/src/signatureSets/attesterSlashings.ts b/packages/state-transition/src/signatureSets/attesterSlashings.ts index 370d48a7d2f0..f0de50e5d0b2 100644 --- a/packages/state-transition/src/signatureSets/attesterSlashings.ts +++ b/packages/state-transition/src/signatureSets/attesterSlashings.ts @@ -1,4 +1,4 @@ -import {allForks, phase0, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types"; import {DOMAIN_BEACON_ATTESTER} from "@lodestar/params"; import {computeSigningRoot, computeStartSlotAtEpoch, ISignatureSet, SignatureSetType} from "../util/index.js"; import {CachedBeaconStateAllForks} from "../types.js"; @@ -6,7 +6,7 @@ import {CachedBeaconStateAllForks} from "../types.js"; /** Get signature sets from all AttesterSlashing objects in a block */ export function getAttesterSlashingsSignatureSets( state: CachedBeaconStateAllForks, - signedBlock: allForks.SignedBeaconBlock + signedBlock: SignedBeaconBlock ): ISignatureSet[] { return signedBlock.message.body.attesterSlashings .map((attesterSlashing) => getAttesterSlashingSignatureSets(state, attesterSlashing)) diff --git a/packages/state-transition/src/signatureSets/index.ts b/packages/state-transition/src/signatureSets/index.ts index 05e4ad4b197a..983e131e00e6 100644 --- a/packages/state-transition/src/signatureSets/index.ts +++ b/packages/state-transition/src/signatureSets/index.ts @@ -1,5 +1,5 @@ import {ForkSeq} from "@lodestar/params"; -import {allForks, altair, capella} from "@lodestar/types"; +import {SignedBeaconBlock, altair, capella} from "@lodestar/types"; import {ISignatureSet} from "../util/index.js"; import {CachedBeaconStateAllForks, CachedBeaconStateAltair} from "../types.js"; import {getSyncCommitteeSignatureSet} from "../block/processSyncCommittee.js"; @@ -25,7 +25,7 @@ export * from "./blsToExecutionChange.js"; */ export function getBlockSignatureSets( state: CachedBeaconStateAllForks, - signedBlock: allForks.SignedBeaconBlock, + signedBlock: SignedBeaconBlock, opts?: { /** Useful since block proposer signature is verified beforehand on gossip validation */ skipProposerSignature?: boolean; diff --git a/packages/state-transition/src/signatureSets/indexedAttestation.ts b/packages/state-transition/src/signatureSets/indexedAttestation.ts index b5c48a20c9d4..9ae6627d0b56 100644 --- a/packages/state-transition/src/signatureSets/indexedAttestation.ts +++ b/packages/state-transition/src/signatureSets/indexedAttestation.ts @@ -1,5 +1,5 @@ import {DOMAIN_BEACON_ATTESTER} from "@lodestar/params"; -import {allForks, phase0, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types"; import { computeSigningRoot, computeStartSlotAtEpoch, @@ -39,7 +39,7 @@ export function getIndexedAttestationSignatureSet( export function getAttestationsSignatureSets( state: CachedBeaconStateAllForks, - signedBlock: allForks.SignedBeaconBlock + signedBlock: SignedBeaconBlock ): ISignatureSet[] { return signedBlock.message.body.attestations.map((attestation) => getIndexedAttestationSignatureSet(state, state.epochCtx.getIndexedAttestation(attestation)) diff --git a/packages/state-transition/src/signatureSets/proposer.ts b/packages/state-transition/src/signatureSets/proposer.ts index 135ac7ed5c7a..b5e501bd16c7 100644 --- a/packages/state-transition/src/signatureSets/proposer.ts +++ b/packages/state-transition/src/signatureSets/proposer.ts @@ -1,12 +1,12 @@ import {DOMAIN_BEACON_PROPOSER} from "@lodestar/params"; -import {allForks, isBlindedBeaconBlock, phase0, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, SignedBlindedBeaconBlock, isBlindedBeaconBlock, phase0, ssz} from "@lodestar/types"; import {computeSigningRoot} from "../util/index.js"; import {ISignatureSet, SignatureSetType, verifySignatureSet} from "../util/signatureSets.js"; import {CachedBeaconStateAllForks} from "../types.js"; export function verifyProposerSignature( state: CachedBeaconStateAllForks, - signedBlock: allForks.FullOrBlindedSignedBeaconBlock + signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock ): boolean { const signatureSet = getBlockProposerSignatureSet(state, signedBlock); return verifySignatureSet(signatureSet); @@ -14,7 +14,7 @@ export function verifyProposerSignature( export function getBlockProposerSignatureSet( state: CachedBeaconStateAllForks, - signedBlock: allForks.FullOrBlindedSignedBeaconBlock + signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock ): ISignatureSet { const {config, epochCtx} = state; const domain = config.getDomain(state.slot, DOMAIN_BEACON_PROPOSER, signedBlock.message.slot); diff --git a/packages/state-transition/src/signatureSets/proposerSlashings.ts b/packages/state-transition/src/signatureSets/proposerSlashings.ts index 46be30c0636e..8a004225111d 100644 --- a/packages/state-transition/src/signatureSets/proposerSlashings.ts +++ b/packages/state-transition/src/signatureSets/proposerSlashings.ts @@ -1,5 +1,5 @@ import {DOMAIN_BEACON_PROPOSER} from "@lodestar/params"; -import {allForks, phase0, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types"; import {computeSigningRoot, ISignatureSet, SignatureSetType} from "../util/index.js"; import {CachedBeaconStateAllForks} from "../types.js"; @@ -33,7 +33,7 @@ export function getProposerSlashingSignatureSets( export function getProposerSlashingsSignatureSets( state: CachedBeaconStateAllForks, - signedBlock: allForks.SignedBeaconBlock + signedBlock: SignedBeaconBlock ): ISignatureSet[] { return signedBlock.message.body.proposerSlashings .map((proposerSlashing) => getProposerSlashingSignatureSets(state, proposerSlashing)) diff --git a/packages/state-transition/src/signatureSets/randao.ts b/packages/state-transition/src/signatureSets/randao.ts index 4fc8b77e4e38..41422a9c1d00 100644 --- a/packages/state-transition/src/signatureSets/randao.ts +++ b/packages/state-transition/src/signatureSets/randao.ts @@ -1,5 +1,5 @@ import {DOMAIN_RANDAO} from "@lodestar/params"; -import {allForks, ssz} from "@lodestar/types"; +import {BeaconBlock, ssz} from "@lodestar/types"; import { computeEpochAtSlot, computeSigningRoot, @@ -9,17 +9,14 @@ import { } from "../util/index.js"; import {CachedBeaconStateAllForks} from "../types.js"; -export function verifyRandaoSignature(state: CachedBeaconStateAllForks, block: allForks.BeaconBlock): boolean { +export function verifyRandaoSignature(state: CachedBeaconStateAllForks, block: BeaconBlock): boolean { return verifySignatureSet(getRandaoRevealSignatureSet(state, block)); } /** * Extract signatures to allow validating all block signatures at once */ -export function getRandaoRevealSignatureSet( - state: CachedBeaconStateAllForks, - block: allForks.BeaconBlock -): ISignatureSet { +export function getRandaoRevealSignatureSet(state: CachedBeaconStateAllForks, block: BeaconBlock): ISignatureSet { const {epochCtx} = state; // should not get epoch from epochCtx const epoch = computeEpochAtSlot(block.slot); diff --git a/packages/state-transition/src/signatureSets/voluntaryExits.ts b/packages/state-transition/src/signatureSets/voluntaryExits.ts index bb86ef41777a..51dd20d671b6 100644 --- a/packages/state-transition/src/signatureSets/voluntaryExits.ts +++ b/packages/state-transition/src/signatureSets/voluntaryExits.ts @@ -1,4 +1,4 @@ -import {allForks, phase0, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types"; import { computeSigningRoot, computeStartSlotAtEpoch, @@ -36,7 +36,7 @@ export function getVoluntaryExitSignatureSet( export function getVoluntaryExitsSignatureSets( state: CachedBeaconStateAllForks, - signedBlock: allForks.SignedBeaconBlock + signedBlock: SignedBeaconBlock ): ISignatureSet[] { return signedBlock.message.body.voluntaryExits.map((voluntaryExit) => getVoluntaryExitSignatureSet(state, voluntaryExit) diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index b3f3b41eb865..3ecd24ea9813 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -1,5 +1,5 @@ import {toHexString} from "@chainsafe/ssz"; -import {allForks, Slot, ssz} from "@lodestar/types"; +import {SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, ssz} from "@lodestar/types"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {BeaconStateTransitionMetrics, onPostStateMetrics, onStateCloneMetrics} from "./metrics.js"; import {beforeProcessEpoch, EpochTransitionCache, EpochTransitionCacheOpts} from "./cache/epochTransitionCache.js"; @@ -59,7 +59,7 @@ export enum StateHashTreeRootSource { */ export function stateTransition( state: CachedBeaconStateAllForks, - signedBlock: allForks.FullOrBlindedSignedBeaconBlock, + signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock, options: StateTransitionOpts = { // Assume default to be valid and available executionPayloadStatus: ExecutionPayloadStatus.valid, diff --git a/packages/state-transition/src/util/blindedBlock.ts b/packages/state-transition/src/util/blindedBlock.ts index 5b6cf42d3cef..2b9510bd1e99 100644 --- a/packages/state-transition/src/util/blindedBlock.ts +++ b/packages/state-transition/src/util/blindedBlock.ts @@ -1,12 +1,27 @@ import {ChainForkConfig} from "@lodestar/config"; -import {ForkSeq} from "@lodestar/params"; -import {allForks, phase0, Root, deneb, isBlindedBeaconBlock, isExecutionPayloadAndBlobsBundle} from "@lodestar/types"; +import {ForkExecution, ForkSeq} from "@lodestar/params"; +import { + Root, + isBlindedBeaconBlock, + isExecutionPayloadAndBlobsBundle, + BeaconBlock, + BeaconBlockHeader, + SignedBeaconBlock, + ExecutionPayload, + ExecutionPayloadAndBlobsBundle, + BlobsBundle, + SignedBeaconBlockOrContents, + Contents, + SignedBlindedBeaconBlock, + BlindedBeaconBlock, + ExecutionPayloadHeader, +} from "@lodestar/types"; import {executionPayloadToPayloadHeader} from "./execution.js"; export function blindedOrFullBlockHashTreeRoot( config: ChainForkConfig, - blindedOrFull: allForks.FullOrBlindedBeaconBlock + blindedOrFull: BeaconBlock | BlindedBeaconBlock ): Root { return isBlindedBeaconBlock(blindedOrFull) ? // Blinded @@ -17,8 +32,8 @@ export function blindedOrFullBlockHashTreeRoot( export function blindedOrFullBlockToHeader( config: ChainForkConfig, - blindedOrFull: allForks.FullOrBlindedBeaconBlock -): phase0.BeaconBlockHeader { + blindedOrFull: BeaconBlock | BlindedBeaconBlock +): BeaconBlockHeader { const bodyRoot = isBlindedBeaconBlock(blindedOrFull) ? // Blinded config.getBlindedForkTypes(blindedOrFull.slot).BeaconBlockBody.hashTreeRoot(blindedOrFull.body) @@ -34,20 +49,17 @@ export function blindedOrFullBlockToHeader( }; } -export function beaconBlockToBlinded( - config: ChainForkConfig, - block: allForks.AllForksExecution["BeaconBlock"] -): allForks.BlindedBeaconBlock { +export function beaconBlockToBlinded(config: ChainForkConfig, block: BeaconBlock): BlindedBeaconBlock { const fork = config.getForkName(block.slot); const executionPayloadHeader = executionPayloadToPayloadHeader(ForkSeq[fork], block.body.executionPayload); - const blindedBlock = {...block, body: {...block.body, executionPayloadHeader}} as allForks.BlindedBeaconBlock; + const blindedBlock = {...block, body: {...block.body, executionPayloadHeader}} as BlindedBeaconBlock; return blindedBlock; } export function signedBlindedBlockToFull( - signedBlindedBlock: allForks.SignedBlindedBeaconBlock, - executionPayload: allForks.ExecutionPayload | null -): allForks.SignedBeaconBlock { + signedBlindedBlock: SignedBlindedBeaconBlock, + executionPayload: ExecutionPayload | null +): SignedBeaconBlock { const signedBlock = { ...signedBlindedBlock, message: { @@ -58,18 +70,18 @@ export function signedBlindedBlockToFull( executionPayload: executionPayload ?? undefined, }, }, - } as allForks.SignedBeaconBlock; + } as SignedBeaconBlock; // state transition can't seem to handle executionPayloadHeader presense in merge block // so just delete the extra field we don't require - delete (signedBlock.message.body as {executionPayloadHeader?: allForks.ExecutionPayloadHeader}) - .executionPayloadHeader; + delete (signedBlock.message.body as {executionPayloadHeader?: ExecutionPayloadHeader}).executionPayloadHeader; return signedBlock; } -export function parseExecutionPayloadAndBlobsBundle( - data: allForks.ExecutionPayload | allForks.ExecutionPayloadAndBlobsBundle -): {executionPayload: allForks.ExecutionPayload; blobsBundle: deneb.BlobsBundle | null} { +export function parseExecutionPayloadAndBlobsBundle(data: ExecutionPayload | ExecutionPayloadAndBlobsBundle): { + executionPayload: ExecutionPayload; + blobsBundle: BlobsBundle | null; +} { if (isExecutionPayloadAndBlobsBundle(data)) { return data; } else { @@ -81,15 +93,15 @@ export function parseExecutionPayloadAndBlobsBundle( } export function reconstructFullBlockOrContents( - signedBlindedBlock: allForks.SignedBlindedBeaconBlock, + signedBlindedBlock: SignedBlindedBeaconBlock, { executionPayload, contents, }: { - executionPayload: allForks.ExecutionPayload | null; - contents: deneb.Contents | null; + executionPayload: ExecutionPayload | null; + contents: Contents | null; } -): allForks.SignedBeaconBlockOrContents { +): SignedBeaconBlockOrContents { const signedBlock = signedBlindedBlockToFull(signedBlindedBlock, executionPayload); if (contents !== null) { @@ -97,8 +109,8 @@ export function reconstructFullBlockOrContents( throw Error("Missing locally produced executionPayload for deneb+ publishBlindedBlock"); } - return {signedBlock, ...contents} as allForks.SignedBeaconBlockOrContents; + return {signedBlock, ...contents} as SignedBeaconBlockOrContents; } else { - return signedBlock as allForks.SignedBeaconBlockOrContents; + return signedBlock as SignedBeaconBlockOrContents; } } diff --git a/packages/state-transition/src/util/blockRoot.ts b/packages/state-transition/src/util/blockRoot.ts index 1e1df38ef4fe..54d96885e675 100644 --- a/packages/state-transition/src/util/blockRoot.ts +++ b/packages/state-transition/src/util/blockRoot.ts @@ -1,6 +1,13 @@ -import {Epoch, Slot, Root, phase0, allForks} from "@lodestar/types"; +import { + Epoch, + Slot, + Root, + BeaconBlock, + SignedBeaconBlock, + BeaconBlockHeader, + SignedBeaconBlockHeader, +} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; - import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; import {ZERO_HASH} from "../constants/index.js"; import {BeaconStateAllForks} from "../types.js"; @@ -28,10 +35,7 @@ export function getBlockRoot(state: BeaconStateAllForks, epoch: Epoch): Root { /** * Return the block header corresponding to a block with ``state_root`` set to ``ZERO_HASH``. */ -export function getTemporaryBlockHeader( - config: ChainForkConfig, - block: allForks.BeaconBlock -): phase0.BeaconBlockHeader { +export function getTemporaryBlockHeader(config: ChainForkConfig, block: BeaconBlock): BeaconBlockHeader { return { slot: block.slot, proposerIndex: block.proposerIndex, @@ -45,7 +49,7 @@ export function getTemporaryBlockHeader( /** * Receives a BeaconBlock, and produces the corresponding BeaconBlockHeader. */ -export function blockToHeader(config: ChainForkConfig, block: allForks.BeaconBlock): phase0.BeaconBlockHeader { +export function blockToHeader(config: ChainForkConfig, block: BeaconBlock): BeaconBlockHeader { return { stateRoot: block.stateRoot, proposerIndex: block.proposerIndex, @@ -57,8 +61,8 @@ export function blockToHeader(config: ChainForkConfig, block: allForks.BeaconBlo export function signedBlockToSignedHeader( config: ChainForkConfig, - signedBlock: allForks.SignedBeaconBlock -): phase0.SignedBeaconBlockHeader { + signedBlock: SignedBeaconBlock +): SignedBeaconBlockHeader { const message = blockToHeader(config, signedBlock.message); const signature = signedBlock.signature; return { diff --git a/packages/state-transition/src/util/epoch.ts b/packages/state-transition/src/util/epoch.ts index b7febf45c110..bb66fb04eb94 100644 --- a/packages/state-transition/src/util/epoch.ts +++ b/packages/state-transition/src/util/epoch.ts @@ -1,5 +1,5 @@ import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, GENESIS_EPOCH, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params"; -import {allForks, Epoch, Slot, SyncPeriod} from "@lodestar/types"; +import {BeaconState, Epoch, Slot, SyncPeriod} from "@lodestar/types"; /** * Return the epoch number at the given slot. @@ -42,14 +42,14 @@ export function computeActivationExitEpoch(epoch: Epoch): Epoch { /** * Return the current epoch of the given state. */ -export function getCurrentEpoch(state: Pick): Epoch { +export function getCurrentEpoch(state: Pick): Epoch { return computeEpochAtSlot(state.slot); } /** * Return the previous epoch of the given state. */ -export function getPreviousEpoch(state: Pick): Epoch { +export function getPreviousEpoch(state: Pick): Epoch { const currentEpoch = getCurrentEpoch(state); if (currentEpoch === GENESIS_EPOCH) { return GENESIS_EPOCH; diff --git a/packages/state-transition/src/util/execution.ts b/packages/state-transition/src/util/execution.ts index 7ac4da4aeecb..1c5046354fcb 100644 --- a/packages/state-transition/src/util/execution.ts +++ b/packages/state-transition/src/util/execution.ts @@ -1,5 +1,18 @@ -import {allForks, bellatrix, capella, deneb, isBlindedBeaconBlockBody, ssz} from "@lodestar/types"; -import {ForkSeq} from "@lodestar/params"; +import { + bellatrix, + capella, + deneb, + isBlindedBeaconBlockBody, + ssz, + BeaconBlock, + BeaconBlockBody, + ExecutionPayload, + isExecutionPayload, + ExecutionPayloadHeader, + BlindedBeaconBlockBody, + BlindedBeaconBlock, +} from "@lodestar/types"; +import {ForkExecution, ForkName, ForkSeq} from "@lodestar/params"; import { BeaconStateBellatrix, @@ -14,7 +27,7 @@ import { * Execution enabled = merge is done. * When (A) state has execution data OR (B) block has execution data */ -export function isExecutionEnabled(state: BeaconStateExecutions, block: allForks.FullOrBlindedBeaconBlock): boolean { +export function isExecutionEnabled(state: BeaconStateExecutions, block: BeaconBlock | BlindedBeaconBlock): boolean { if (isMergeTransitionComplete(state)) { return true; } @@ -85,43 +98,33 @@ export function isExecutionCachedStateType(state: CachedBeaconStateAllForks): st return (state as CachedBeaconStateExecutions).latestExecutionPayloadHeader !== undefined; } -/** Type guard for allForks.ExecutionBlockBody */ -export function isExecutionBlockBodyType( - blockBody: allForks.BeaconBlockBody -): blockBody is allForks.ExecutionBlockBody { - return (blockBody as allForks.ExecutionBlockBody).executionPayload !== undefined; +/** Type guard for ExecutionBlockBody */ +export function isExecutionBlockBodyType(blockBody: BeaconBlockBody): blockBody is BeaconBlockBody { + return (blockBody as BeaconBlockBody).executionPayload !== undefined; } -export function getFullOrBlindedPayload( - block: allForks.FullOrBlindedBeaconBlock -): allForks.FullOrBlindedExecutionPayload { +export function getFullOrBlindedPayload(block: BeaconBlock): ExecutionPayload | ExecutionPayloadHeader { return getFullOrBlindedPayloadFromBody(block.body); } export function getFullOrBlindedPayloadFromBody( - body: allForks.FullOrBlindedBeaconBlockBody -): allForks.FullOrBlindedExecutionPayload { + body: BeaconBlockBody | BlindedBeaconBlockBody +): ExecutionPayload | ExecutionPayloadHeader { if (isBlindedBeaconBlockBody(body)) { return body.executionPayloadHeader; } else if ((body as bellatrix.BeaconBlockBody).executionPayload !== undefined) { return (body as bellatrix.BeaconBlockBody).executionPayload; } else { - throw Error("Ǹot allForks.FullOrBlindedBeaconBlock"); + throw Error("Not full or blinded beacon block"); } } -export function isExecutionPayload( - payload: allForks.FullOrBlindedExecutionPayload -): payload is allForks.ExecutionPayload { - return (payload as allForks.ExecutionPayload).transactions !== undefined; -} - export function isCapellaPayload( - payload: allForks.FullOrBlindedExecutionPayload -): payload is capella.FullOrBlindedExecutionPayload { + payload: ExecutionPayload | ExecutionPayloadHeader +): payload is ExecutionPayload | ExecutionPayloadHeader { return ( - (payload as capella.ExecutionPayload).withdrawals !== undefined || - (payload as capella.ExecutionPayloadHeader).withdrawalsRoot !== undefined + (payload as ExecutionPayload).withdrawals !== undefined || + (payload as ExecutionPayloadHeader).withdrawalsRoot !== undefined ); } @@ -131,13 +134,10 @@ export function isCapellaPayloadHeader( return (payload as capella.ExecutionPayloadHeader).withdrawalsRoot !== undefined; } -export function executionPayloadToPayloadHeader( - fork: ForkSeq, - payload: allForks.ExecutionPayload -): allForks.ExecutionPayloadHeader { +export function executionPayloadToPayloadHeader(fork: ForkSeq, payload: ExecutionPayload): ExecutionPayloadHeader { const transactionsRoot = ssz.bellatrix.Transactions.hashTreeRoot(payload.transactions); - const bellatrixPayloadFields: allForks.ExecutionPayloadHeader = { + const bellatrixPayloadFields: ExecutionPayloadHeader = { parentHash: payload.parentHash, feeRecipient: payload.feeRecipient, stateRoot: payload.stateRoot, diff --git a/packages/state-transition/src/util/sszBytes.ts b/packages/state-transition/src/util/sszBytes.ts index b5141e1673e5..fe3e5b69b892 100644 --- a/packages/state-transition/src/util/sszBytes.ts +++ b/packages/state-transition/src/util/sszBytes.ts @@ -1,6 +1,6 @@ import {ChainForkConfig} from "@lodestar/config"; -import {ForkSeq} from "@lodestar/params"; -import {Slot, allForks} from "@lodestar/types"; +import {ForkAll, ForkSeq} from "@lodestar/params"; +import {SSZTypesFor, Slot} from "@lodestar/types"; import {bytesToInt} from "@lodestar/utils"; /** @@ -42,10 +42,7 @@ export function getForkFromStateBytes(config: ChainForkConfig, bytes: Uint8Array return config.getForkSeq(slot); } -export function getStateTypeFromBytes( - config: ChainForkConfig, - bytes: Uint8Array -): allForks.AllForksSSZTypes["BeaconState"] { +export function getStateTypeFromBytes(config: ChainForkConfig, bytes: Uint8Array): SSZTypesFor { const slot = getStateSlotFromBytes(bytes); return config.getForkTypes(slot).BeaconState; } diff --git a/packages/state-transition/src/util/weakSubjectivity.ts b/packages/state-transition/src/util/weakSubjectivity.ts index 4614534bcb27..6bd6636c3e70 100644 --- a/packages/state-transition/src/util/weakSubjectivity.ts +++ b/packages/state-transition/src/util/weakSubjectivity.ts @@ -5,7 +5,7 @@ import {Epoch, Root} from "@lodestar/types"; import {ssz} from "@lodestar/types"; import {Checkpoint} from "@lodestar/types/phase0"; import {ZERO_HASH} from "../constants/constants.js"; -import {CachedBeaconStateAllForks, BeaconStateAllForks} from "../types.js"; +import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js"; import {computeEpochAtSlot, getCurrentEpoch, computeCheckpointEpochAtStateSlot} from "./epoch.js"; import {getCurrentSlot} from "./slot.js"; import {getActiveValidatorIndices, getChurnLimit} from "./validator.js"; diff --git a/packages/state-transition/test/perf/analyzeEpochs.ts b/packages/state-transition/test/perf/analyzeEpochs.ts index 4b793fe95e6e..c2f09fcc5521 100644 --- a/packages/state-transition/test/perf/analyzeEpochs.ts +++ b/packages/state-transition/test/perf/analyzeEpochs.ts @@ -143,7 +143,7 @@ async function analyzeEpochs(network: NetworkName, fromEpoch?: number): Promise< currentEpochAttestationsBits: countAttBits(currentEpochAttestations as phase0.PendingAttestation[]), }); - // -- allForks + // -- all forks // processEffectiveBalanceUpdates: function of effectiveBalance changes // processEth1DataReset: free // processHistoricalRootsUpdate: free diff --git a/packages/state-transition/test/perf/types.ts b/packages/state-transition/test/perf/types.ts index 23b9667716d9..8f4914238dba 100644 --- a/packages/state-transition/test/perf/types.ts +++ b/packages/state-transition/test/perf/types.ts @@ -1,4 +1,4 @@ -import {allForks} from "@lodestar/types"; +import {SignedBeaconBlock} from "@lodestar/types"; import {CachedBeaconStateAllForks, CachedBeaconStatePhase0, CachedBeaconStateAltair} from "../../src/index.js"; import {EpochTransitionCache} from "../../src/types.js"; @@ -6,7 +6,7 @@ import {EpochTransitionCache} from "../../src/types.js"; export type State = CachedBeaconStateAllForks; export type StateAltair = CachedBeaconStateAltair; -export type StateBlock = {state: CachedBeaconStateAllForks; block: allForks.SignedBeaconBlock}; +export type StateBlock = {state: CachedBeaconStateAllForks; block: SignedBeaconBlock}; export type StateEpoch = {state: CachedBeaconStateAllForks; cache: EpochTransitionCache}; export type StatePhase0Epoch = {state: CachedBeaconStatePhase0; cache: EpochTransitionCache}; export type StateAltairEpoch = {state: CachedBeaconStateAltair; cache: EpochTransitionCache}; diff --git a/packages/state-transition/test/perf/util.ts b/packages/state-transition/test/perf/util.ts index 4df6746ea938..a66797443d85 100644 --- a/packages/state-transition/test/perf/util.ts +++ b/packages/state-transition/test/perf/util.ts @@ -1,12 +1,13 @@ import {CoordType, PublicKey, SecretKey} from "@chainsafe/bls/types"; import bls from "@chainsafe/bls"; import {BitArray, fromHexString} from "@chainsafe/ssz"; -import {allForks, phase0, ssz, Slot, altair} from "@lodestar/types"; +import {phase0, ssz, Slot, BeaconState} from "@lodestar/types"; import {config} from "@lodestar/config/default"; import {createBeaconConfig, createChainForkConfig} from "@lodestar/config"; import { EPOCHS_PER_ETH1_VOTING_PERIOD, EPOCHS_PER_HISTORICAL_VECTOR, + ForkName, MAX_ATTESTATIONS, MAX_EFFECTIVE_BALANCE, SLOTS_PER_EPOCH, @@ -255,7 +256,7 @@ export function generatePerformanceStateAltair(pubkeysArg?: Uint8Array[]): Beaco if (!altairState) { const pubkeys = pubkeysArg || getPubkeys().pubkeys; const statePhase0 = buildPerformanceStatePhase0(pubkeys); - const state = statePhase0 as allForks.BeaconState as altair.BeaconState; + const state = statePhase0 as BeaconState as BeaconState; state.previousEpochParticipation = newFilledArray(pubkeys.length, 0b111); state.currentEpochParticipation = state.previousEpochParticipation; diff --git a/packages/state-transition/test/utils/testFileCache.ts b/packages/state-transition/test/utils/testFileCache.ts index 6e554d888d18..b894674f54f6 100644 --- a/packages/state-transition/test/utils/testFileCache.ts +++ b/packages/state-transition/test/utils/testFileCache.ts @@ -4,7 +4,7 @@ import got from "got"; import {getClient} from "@lodestar/api"; import {NetworkName, networksChainConfig} from "@lodestar/config/networks"; import {createChainForkConfig, ChainForkConfig} from "@lodestar/config"; -import {allForks} from "@lodestar/types"; +import {SignedBeaconBlock} from "@lodestar/types"; import {CachedBeaconStateAllForks} from "../../src/index.js"; import {testCachePath} from "../cache.js"; import {createCachedBeaconStateTest} from "../utils/state.js"; @@ -67,7 +67,7 @@ export async function getNetworkCachedBlock( network: NetworkName, slot: number, timeout?: number -): Promise { +): Promise { const config = getNetworkConfig(network); const fileId = `block_${network}_${slot}.ssz`; diff --git a/packages/types/README.md b/packages/types/README.md index 749f93321d39..59c1a02e122f 100644 --- a/packages/types/README.md +++ b/packages/types/README.md @@ -67,13 +67,13 @@ import {Epoch, ssz} from "@lodestar/types"; const epoch: Epoch = ssz.Epoch.defaultValue(); ``` -In some cases, we need interfaces that accept types across all forks, like when the fork is not known ahead of time. Typescript interfaces for this purpose are exported under the `allForks` namespace. SSZ Types typed to these interfaces are also provided under an `allForks` namespace, but keyed by `ForkName`. +In some cases, we need interfaces that accept types across all forks, like when the fork is not known ahead of time. SSZ Types typed to these interfaces are also provided under an `allForks` namespace, but keyed by `ForkName`. ```typescript import {ForkName} from "@lodestar/params"; -import {allForks, ssz} from "@lodestar/types"; +import {ssz, BeaconState} from "@lodestar/types"; -const state: allForks.BeaconState = ssz.allForks[ForkName.phase0].BeaconState.defaultValue(); +const state: BeaconState = ssz.allForks[ForkName.phase0].BeaconState.defaultValue(); ``` ## License diff --git a/packages/types/package.json b/packages/types/package.json index b892e6f0285a..037a3c46d71b 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -17,9 +17,6 @@ ".": { "import": "./lib/index.js" }, - "./allForks": { - "import": "./lib/allForks/index.js" - }, "./altair": { "import": "./lib/altair/index.js" }, diff --git a/packages/types/src/allForks/index.ts b/packages/types/src/allForks/index.ts deleted file mode 100644 index 104de77d50c3..000000000000 --- a/packages/types/src/allForks/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from "./types.js"; - -// We have to use import->export because of the limitation in `@microsoft/api-extractor` -// which is used to bundle the package types -import * as ts from "./types.js"; -import * as ssz from "./sszTypes.js"; -export {ts, ssz}; diff --git a/packages/types/src/allForks/sszTypes.ts b/packages/types/src/allForks/sszTypes.ts deleted file mode 100644 index 7174bc52e89c..000000000000 --- a/packages/types/src/allForks/sszTypes.ts +++ /dev/null @@ -1,160 +0,0 @@ -import {ssz as phase0} from "../phase0/index.js"; -import {ssz as altair} from "../altair/index.js"; -import {ssz as bellatrix} from "../bellatrix/index.js"; -import {ssz as capella} from "../capella/index.js"; -import {ssz as deneb} from "../deneb/index.js"; - -/** - * Index the ssz types that differ by fork - * A record of AllForksSSZTypes indexed by fork - */ -export const allForks = { - phase0: { - BeaconBlockBody: phase0.BeaconBlockBody, - BeaconBlock: phase0.BeaconBlock, - SignedBeaconBlock: phase0.SignedBeaconBlock, - BeaconState: phase0.BeaconState, - Metadata: phase0.Metadata, - }, - altair: { - BeaconBlockBody: altair.BeaconBlockBody, - BeaconBlock: altair.BeaconBlock, - SignedBeaconBlock: altair.SignedBeaconBlock, - BeaconState: altair.BeaconState, - Metadata: altair.Metadata, - }, - bellatrix: { - BeaconBlockBody: bellatrix.BeaconBlockBody, - BeaconBlock: bellatrix.BeaconBlock, - SignedBeaconBlock: bellatrix.SignedBeaconBlock, - BeaconState: bellatrix.BeaconState, - Metadata: altair.Metadata, - }, - capella: { - BeaconBlockBody: capella.BeaconBlockBody, - BeaconBlock: capella.BeaconBlock, - SignedBeaconBlock: capella.SignedBeaconBlock, - BeaconState: capella.BeaconState, - Metadata: altair.Metadata, - }, - deneb: { - BeaconBlockBody: deneb.BeaconBlockBody, - BeaconBlock: deneb.BeaconBlock, - SignedBeaconBlock: deneb.SignedBeaconBlock, - BeaconState: deneb.BeaconState, - Metadata: altair.Metadata, - }, -}; - -/** - * Index the execution ssz types that differ by fork - * A record of AllForksExecutionSSZTypes indexed by fork - */ -export const allForksExecution = { - bellatrix: { - BeaconBlockBody: bellatrix.BeaconBlockBody, - BeaconBlock: bellatrix.BeaconBlock, - SignedBeaconBlock: bellatrix.SignedBeaconBlock, - BeaconState: bellatrix.BeaconState, - ExecutionPayload: bellatrix.ExecutionPayload, - ExecutionPayloadHeader: bellatrix.ExecutionPayloadHeader, - BuilderBid: bellatrix.BuilderBid, - SignedBuilderBid: bellatrix.SignedBuilderBid, - SSEPayloadAttributes: bellatrix.SSEPayloadAttributes, - }, - capella: { - BeaconBlockBody: capella.BeaconBlockBody, - BeaconBlock: capella.BeaconBlock, - SignedBeaconBlock: capella.SignedBeaconBlock, - BeaconState: capella.BeaconState, - // Not used in phase0 but added for type consitency - ExecutionPayload: capella.ExecutionPayload, - ExecutionPayloadHeader: capella.ExecutionPayloadHeader, - BuilderBid: capella.BuilderBid, - SignedBuilderBid: capella.SignedBuilderBid, - SSEPayloadAttributes: capella.SSEPayloadAttributes, - }, - deneb: { - BeaconBlockBody: deneb.BeaconBlockBody, - BeaconBlock: deneb.BeaconBlock, - SignedBeaconBlock: deneb.SignedBeaconBlock, - BeaconState: deneb.BeaconState, - ExecutionPayload: deneb.ExecutionPayload, - ExecutionPayloadHeader: deneb.ExecutionPayloadHeader, - BuilderBid: deneb.BuilderBid, - SignedBuilderBid: deneb.SignedBuilderBid, - SSEPayloadAttributes: deneb.SSEPayloadAttributes, - }, -}; - -/** - * Index the blinded ssz types that differ by fork - * A record of AllForksBlindedSSZTypes indexed by fork - */ -export const allForksBlinded = { - bellatrix: { - BeaconBlockBody: bellatrix.BlindedBeaconBlockBody, - BeaconBlock: bellatrix.BlindedBeaconBlock, - SignedBeaconBlock: bellatrix.SignedBlindedBeaconBlock, - }, - capella: { - BeaconBlockBody: capella.BlindedBeaconBlockBody, - BeaconBlock: capella.BlindedBeaconBlock, - SignedBeaconBlock: capella.SignedBlindedBeaconBlock, - }, - deneb: { - BeaconBlockBody: deneb.BlindedBeaconBlockBody, - BeaconBlock: deneb.BlindedBeaconBlock, - SignedBeaconBlock: deneb.SignedBlindedBeaconBlock, - }, -}; - -export const allForksLightClient = { - altair: { - BeaconBlock: altair.BeaconBlock, - BeaconBlockBody: altair.BeaconBlockBody, - LightClientHeader: altair.LightClientHeader, - LightClientBootstrap: altair.LightClientBootstrap, - LightClientUpdate: altair.LightClientUpdate, - LightClientFinalityUpdate: altair.LightClientFinalityUpdate, - LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate, - LightClientStore: altair.LightClientStore, - }, - bellatrix: { - BeaconBlock: bellatrix.BeaconBlock, - BeaconBlockBody: bellatrix.BeaconBlockBody, - LightClientHeader: altair.LightClientHeader, - LightClientBootstrap: altair.LightClientBootstrap, - LightClientUpdate: altair.LightClientUpdate, - LightClientFinalityUpdate: altair.LightClientFinalityUpdate, - LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate, - LightClientStore: altair.LightClientStore, - }, - capella: { - BeaconBlock: capella.BeaconBlock, - BeaconBlockBody: capella.BeaconBlockBody, - LightClientHeader: capella.LightClientHeader, - LightClientBootstrap: capella.LightClientBootstrap, - LightClientUpdate: capella.LightClientUpdate, - LightClientFinalityUpdate: capella.LightClientFinalityUpdate, - LightClientOptimisticUpdate: capella.LightClientOptimisticUpdate, - LightClientStore: capella.LightClientStore, - }, - deneb: { - BeaconBlock: deneb.BeaconBlock, - BeaconBlockBody: deneb.BeaconBlockBody, - LightClientHeader: deneb.LightClientHeader, - LightClientBootstrap: deneb.LightClientBootstrap, - LightClientUpdate: deneb.LightClientUpdate, - LightClientFinalityUpdate: deneb.LightClientFinalityUpdate, - LightClientOptimisticUpdate: deneb.LightClientOptimisticUpdate, - LightClientStore: deneb.LightClientStore, - }, -}; - -export const allForksBlobs = { - deneb: { - BlobSidecar: deneb.BlobSidecar, - ExecutionPayloadAndBlobsBundle: deneb.ExecutionPayloadAndBlobsBundle, - }, -}; diff --git a/packages/types/src/allForks/types.ts b/packages/types/src/allForks/types.ts deleted file mode 100644 index 59768a5a3308..000000000000 --- a/packages/types/src/allForks/types.ts +++ /dev/null @@ -1,298 +0,0 @@ -import {CompositeType, ContainerType, ValueOf, CompositeView, CompositeViewDU} from "@chainsafe/ssz"; -import {ts as phase0} from "../phase0/index.js"; -import {ts as altair} from "../altair/index.js"; -import {ts as bellatrix} from "../bellatrix/index.js"; -import {ts as capella} from "../capella/index.js"; -import {ts as deneb} from "../deneb/index.js"; - -import {ssz as phase0Ssz} from "../phase0/index.js"; -import {ssz as altairSsz} from "../altair/index.js"; -import {ssz as bellatrixSsz} from "../bellatrix/index.js"; -import {ssz as capellaSsz} from "../capella/index.js"; -import {ssz as denebSsz} from "../deneb/index.js"; - -// Re-export union types for types that are _known_ to differ - -export type BeaconBlockBody = - | phase0.BeaconBlockBody - | altair.BeaconBlockBody - | bellatrix.BeaconBlockBody - | capella.BeaconBlockBody - | deneb.BeaconBlockBody; -export type BeaconBlock = - | phase0.BeaconBlock - | altair.BeaconBlock - | bellatrix.BeaconBlock - | capella.BeaconBlock - | deneb.BeaconBlock; -export type SignedBeaconBlock = - | phase0.SignedBeaconBlock - | altair.SignedBeaconBlock - | bellatrix.SignedBeaconBlock - | capella.SignedBeaconBlock - | deneb.SignedBeaconBlock; -export type BeaconState = - | phase0.BeaconState - | altair.BeaconState - | bellatrix.BeaconState - | capella.BeaconState - | deneb.BeaconState; -export type Metadata = phase0.Metadata | altair.Metadata; - -// For easy reference in the assemble block for building payloads -export type ExecutionBlockBody = bellatrix.BeaconBlockBody | capella.BeaconBlockBody | deneb.BeaconBlockBody; - -// These two additional types will also change bellatrix forward -export type ExecutionPayload = bellatrix.ExecutionPayload | capella.ExecutionPayload | deneb.ExecutionPayload; -export type ExecutionPayloadHeader = - | bellatrix.ExecutionPayloadHeader - | capella.ExecutionPayloadHeader - | deneb.ExecutionPayloadHeader; - -// Blinded types that will change across forks -export type BlindedBeaconBlockBody = - | bellatrix.BlindedBeaconBlockBody - | capella.BlindedBeaconBlockBody - | deneb.BlindedBeaconBlockBody; -export type BlindedBeaconBlock = bellatrix.BlindedBeaconBlock | capella.BlindedBeaconBlock | deneb.BlindedBeaconBlock; -export type SignedBlindedBeaconBlock = - | bellatrix.SignedBlindedBeaconBlock - | capella.SignedBlindedBeaconBlock - | deneb.SignedBlindedBeaconBlock; - -// Full or blinded types -export type FullOrBlindedExecutionPayload = - | bellatrix.FullOrBlindedExecutionPayload - | capella.FullOrBlindedExecutionPayload; -export type FullOrBlindedBeaconBlockBody = BeaconBlockBody | BlindedBeaconBlockBody; -export type FullOrBlindedBeaconBlock = BeaconBlock | BlindedBeaconBlock; -export type FullOrBlindedSignedBeaconBlock = SignedBeaconBlock | SignedBlindedBeaconBlock; - -export type BlockContents = {block: BeaconBlock; kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs}; -export type SignedBlockContents = { - signedBlock: SignedBeaconBlock; - kzgProofs: deneb.KZGProofs; - blobs: deneb.Blobs; -}; - -export type BeaconBlockOrContents = BeaconBlock | BlockContents; -export type SignedBeaconBlockOrContents = SignedBeaconBlock | SignedBlockContents; - -export type FullOrBlindedBeaconBlockOrContents = BeaconBlockOrContents | BlindedBeaconBlock; - -export type BuilderBid = bellatrix.BuilderBid | capella.BuilderBid | deneb.BuilderBid; -export type SignedBuilderBid = bellatrix.SignedBuilderBid | capella.SignedBuilderBid | deneb.SignedBuilderBid; -export type ExecutionPayloadAndBlobsBundle = deneb.ExecutionPayloadAndBlobsBundle; - -export type LightClientHeader = altair.LightClientHeader | capella.LightClientHeader | deneb.LightClientHeader; -export type LightClientBootstrap = - | altair.LightClientBootstrap - | capella.LightClientBootstrap - | deneb.LightClientBootstrap; -export type LightClientUpdate = altair.LightClientUpdate | capella.LightClientUpdate | deneb.LightClientUpdate; -export type LightClientFinalityUpdate = - | altair.LightClientFinalityUpdate - | capella.LightClientFinalityUpdate - | deneb.LightClientFinalityUpdate; -export type LightClientOptimisticUpdate = - | altair.LightClientOptimisticUpdate - | capella.LightClientOptimisticUpdate - | deneb.LightClientOptimisticUpdate; -export type LightClientStore = altair.LightClientStore | capella.LightClientStore | deneb.LightClientStore; - -export type SSEPayloadAttributes = - | bellatrix.SSEPayloadAttributes - | capella.SSEPayloadAttributes - | deneb.SSEPayloadAttributes; - -/** - * Types known to change between forks - */ -export type AllForksTypes = { - BeaconBlockBody: BeaconBlockBody; - BeaconBlock: BeaconBlock; - SignedBeaconBlock: SignedBeaconBlock; - BeaconState: BeaconState; - Metadata: Metadata; - ExecutionPayload: ExecutionPayload; - ExecutionPayloadHeader: ExecutionPayloadHeader; - LightClientHeader: LightClientHeader; - BuilderBid: BuilderBid; - SignedBuilderBid: SignedBuilderBid; -}; - -export type AllForksBlindedTypes = { - BeaconBlockBody: BlindedBeaconBlockBody; - BeaconBlock: BlindedBeaconBlock; - SignedBeaconBlock: SignedBlindedBeaconBlock; -}; - -export type AllForksLightClient = { - BeaconBlock: altair.BeaconBlock | bellatrix.BeaconBlock | capella.BeaconBlock | deneb.BeaconBlock; - LightClientHeader: LightClientHeader; - LightClientBootstrap: LightClientBootstrap; - LightClientUpdate: LightClientUpdate; - LightClientFinalityUpdate: LightClientFinalityUpdate; - LightClientOptimisticUpdate: LightClientOptimisticUpdate; - LightClientStore: LightClientStore; -}; - -export type AllForksExecution = { - BeaconBlock: bellatrix.BeaconBlock | capella.BeaconBlock | deneb.BeaconBlock; - BeaconBlockBody: bellatrix.BeaconBlockBody | capella.BeaconBlockBody | deneb.BeaconBlockBody; -}; - -/** - * An AllForks type must accept as any parameter the UNION of all fork types. - * The generic argument of `AllForksTypeOf` must be the union of the fork types: - * - * - * For example, `allForks.BeaconState.defaultValue()` must return - * ``` - * phase0.BeaconState | altair.BeaconState | bellatrix.BeaconState - * ``` - * - * And `allForks.BeaconState.serialize()` must accept as parameter - * ``` - * phase0.BeaconState | altair.BeaconState | bellatrix.BeaconState - * ``` - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type AllForksTypeOf> = CompositeType< - ValueOf, - CompositeView, - CompositeViewDU ->; - -/** - * SSZ Types known to change between forks. - * - * Re-wrapping a union of fields in a new ContainerType allows to pass a generic block to .serialize() - * - .serialize() requires a value with ONLY the common fork fields - * - .deserialize() and ValueOf return a value with ONLY the general fork fields - */ -export type AllForksSSZTypes = { - BeaconBlockBody: AllForksTypeOf< - | typeof phase0Ssz.BeaconBlockBody - | typeof altairSsz.BeaconBlockBody - | typeof bellatrixSsz.BeaconBlockBody - | typeof capellaSsz.BeaconBlockBody - | typeof denebSsz.BeaconBlockBody - >; - BeaconBlock: AllForksTypeOf< - | typeof phase0Ssz.BeaconBlock - | typeof altairSsz.BeaconBlock - | typeof bellatrixSsz.BeaconBlock - | typeof capellaSsz.BeaconBlock - | typeof denebSsz.BeaconBlock - >; - SignedBeaconBlock: AllForksTypeOf< - | typeof phase0Ssz.SignedBeaconBlock - | typeof altairSsz.SignedBeaconBlock - | typeof bellatrixSsz.SignedBeaconBlock - | typeof capellaSsz.SignedBeaconBlock - | typeof denebSsz.SignedBeaconBlock - >; - BeaconState: AllForksTypeOf< - | typeof phase0Ssz.BeaconState - | typeof altairSsz.BeaconState - | typeof bellatrixSsz.BeaconState - | typeof capellaSsz.BeaconState - | typeof denebSsz.BeaconState - >; - Metadata: AllForksTypeOf; -}; - -export type AllForksExecutionSSZTypes = { - BeaconBlockBody: AllForksTypeOf< - typeof bellatrixSsz.BeaconBlockBody | typeof capellaSsz.BeaconBlockBody | typeof denebSsz.BeaconBlockBody - >; - BeaconBlock: AllForksTypeOf< - typeof bellatrixSsz.BeaconBlock | typeof capellaSsz.BeaconBlock | typeof denebSsz.BeaconBlock - >; - SignedBeaconBlock: AllForksTypeOf< - typeof bellatrixSsz.SignedBeaconBlock | typeof capellaSsz.SignedBeaconBlock | typeof denebSsz.SignedBeaconBlock - >; - BeaconState: AllForksTypeOf< - typeof bellatrixSsz.BeaconState | typeof capellaSsz.BeaconState | typeof denebSsz.BeaconState - >; - ExecutionPayload: AllForksTypeOf< - typeof bellatrixSsz.ExecutionPayload | typeof capellaSsz.ExecutionPayload | typeof denebSsz.ExecutionPayload - >; - ExecutionPayloadHeader: AllForksTypeOf< - | typeof bellatrixSsz.ExecutionPayloadHeader - | typeof capellaSsz.ExecutionPayloadHeader - | typeof denebSsz.ExecutionPayloadHeader - >; - BuilderBid: AllForksTypeOf< - typeof bellatrixSsz.BuilderBid | typeof capellaSsz.BuilderBid | typeof denebSsz.BuilderBid - >; - SignedBuilderBid: AllForksTypeOf< - typeof bellatrixSsz.SignedBuilderBid | typeof capellaSsz.SignedBuilderBid | typeof denebSsz.SignedBuilderBid - >; - SSEPayloadAttributes: AllForksTypeOf< - | typeof bellatrixSsz.SSEPayloadAttributes - | typeof capellaSsz.SSEPayloadAttributes - | typeof denebSsz.SSEPayloadAttributes - >; -}; - -export type AllForksBlindedSSZTypes = { - BeaconBlockBody: AllForksTypeOf< - | typeof bellatrixSsz.BlindedBeaconBlockBody - | typeof capellaSsz.BlindedBeaconBlock - | typeof denebSsz.BlindedBeaconBlock - >; - BeaconBlock: AllForksTypeOf< - typeof bellatrixSsz.BlindedBeaconBlock | typeof capellaSsz.BlindedBeaconBlock | typeof denebSsz.BlindedBeaconBlock - >; - SignedBeaconBlock: AllForksTypeOf< - | typeof bellatrixSsz.SignedBlindedBeaconBlock - | typeof capellaSsz.SignedBlindedBeaconBlock - | typeof denebSsz.SignedBlindedBeaconBlock - >; -}; - -export type AllForksLightClientSSZTypes = { - BeaconBlock: AllForksTypeOf< - | typeof altairSsz.BeaconBlock - | typeof bellatrixSsz.BeaconBlock - | typeof capellaSsz.BeaconBlock - | typeof denebSsz.BeaconBlock - >; - BeaconBlockBody: AllForksTypeOf< - | typeof altairSsz.BeaconBlockBody - | typeof bellatrixSsz.BeaconBlockBody - | typeof capellaSsz.BeaconBlockBody - | typeof denebSsz.BeaconBlockBody - >; - LightClientHeader: AllForksTypeOf< - typeof altairSsz.LightClientHeader | typeof capellaSsz.LightClientHeader | typeof denebSsz.LightClientHeader - >; - LightClientBootstrap: AllForksTypeOf< - | typeof altairSsz.LightClientBootstrap - | typeof capellaSsz.LightClientBootstrap - | typeof denebSsz.LightClientBootstrap - >; - LightClientUpdate: AllForksTypeOf< - typeof altairSsz.LightClientUpdate | typeof capellaSsz.LightClientUpdate | typeof denebSsz.LightClientUpdate - >; - LightClientFinalityUpdate: AllForksTypeOf< - | typeof altairSsz.LightClientFinalityUpdate - | typeof capellaSsz.LightClientFinalityUpdate - | typeof denebSsz.LightClientFinalityUpdate - >; - LightClientOptimisticUpdate: AllForksTypeOf< - | typeof altairSsz.LightClientOptimisticUpdate - | typeof capellaSsz.LightClientOptimisticUpdate - | typeof denebSsz.LightClientOptimisticUpdate - >; - LightClientStore: AllForksTypeOf< - typeof altairSsz.LightClientStore | typeof capellaSsz.LightClientStore | typeof denebSsz.LightClientStore - >; -}; - -export type AllForksBlobsSSZTypes = { - BlobSidecar: AllForksTypeOf; - ExecutionPayloadAndBlobsBundle: AllForksTypeOf; -}; diff --git a/packages/types/src/deneb/types.ts b/packages/types/src/deneb/types.ts index 0921ae2428e7..9a901c9a1a81 100644 --- a/packages/types/src/deneb/types.ts +++ b/packages/types/src/deneb/types.ts @@ -1,5 +1,6 @@ import {ValueOf} from "@chainsafe/ssz"; -import {BlockContents} from "../allForks/types.js"; +import {ForkName} from "@lodestar/params"; +import type {BlockContents} from "../types.js"; import * as ssz from "./sszTypes.js"; export type KZGProof = ValueOf; @@ -48,4 +49,4 @@ export type LightClientOptimisticUpdate = ValueOf; export type ProducedBlobSidecars = Omit; -export type Contents = Omit; +export type Contents = Omit, "block">; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index bfd0f6abb6f9..7838a02d06e2 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,6 +1,8 @@ export * from "./types.js"; import * as ssz from "./sszTypes.js"; -export {ssz}; +import {sszTypesFor} from "./sszTypes.js"; +import type {SSZBlindedTypesFor, SSZTypesFor} from "./sszTypes.js"; +export {sszTypesFor, SSZBlindedTypesFor, SSZTypesFor, ssz}; // Typeguards export * from "./utils/typeguards.js"; // String type diff --git a/packages/types/src/sszTypes.ts b/packages/types/src/sszTypes.ts index 2a7df948a447..1ee5b9340630 100644 --- a/packages/types/src/sszTypes.ts +++ b/packages/types/src/sszTypes.ts @@ -1,13 +1,185 @@ +import {CompositeType, CompositeView, CompositeViewDU, ContainerType, ValueOf} from "@chainsafe/ssz"; +import {ForkBlobs, ForkExecution, ForkLightClient, ForkName} from "@lodestar/params"; +import {ssz as phase0} from "./phase0/index.js"; +import {ssz as altair} from "./altair/index.js"; +import {ssz as bellatrix} from "./bellatrix/index.js"; +import {ssz as capella} from "./capella/index.js"; +import {ssz as deneb} from "./deneb/index.js"; + export * from "./primitive/sszTypes.js"; -export {ssz as phase0} from "./phase0/index.js"; -export {ssz as altair} from "./altair/index.js"; -export {ssz as bellatrix} from "./bellatrix/index.js"; -export {ssz as capella} from "./capella/index.js"; -export {ssz as deneb} from "./deneb/index.js"; - -import {ssz as allForksSsz} from "./allForks/index.js"; -export const allForks = allForksSsz.allForks; -export const allForksBlinded = allForksSsz.allForksBlinded; -export const allForksExecution = allForksSsz.allForksExecution; -export const allForksBlobs = allForksSsz.allForksBlobs; -export const allForksLightClient = allForksSsz.allForksLightClient; +export {phase0, altair, bellatrix, capella, deneb}; + +/** + * Index the ssz types that differ by fork + * A record of AllForksSSZTypes indexed by fork + */ +const typesByFork = { + [ForkName.phase0]: { + BeaconBlock: phase0.BeaconBlock, + BeaconBlockBody: phase0.BeaconBlockBody, + BeaconState: phase0.BeaconState, + SignedBeaconBlock: phase0.SignedBeaconBlock, + Metadata: phase0.Metadata, + }, + [ForkName.altair]: { + BeaconBlock: altair.BeaconBlock, + BeaconBlockBody: altair.BeaconBlockBody, + BeaconState: altair.BeaconState, + SignedBeaconBlock: altair.SignedBeaconBlock, + Metadata: altair.Metadata, + LightClientHeader: altair.LightClientHeader, + LightClientBootstrap: altair.LightClientBootstrap, + LightClientUpdate: altair.LightClientUpdate, + LightClientFinalityUpdate: altair.LightClientFinalityUpdate, + LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate, + LightClientStore: altair.LightClientStore, + }, + [ForkName.bellatrix]: { + BeaconBlock: bellatrix.BeaconBlock, + BeaconBlockBody: bellatrix.BeaconBlockBody, + BeaconState: bellatrix.BeaconState, + SignedBeaconBlock: bellatrix.SignedBeaconBlock, + Metadata: altair.Metadata, + LightClientHeader: altair.LightClientHeader, + LightClientBootstrap: altair.LightClientBootstrap, + LightClientUpdate: altair.LightClientUpdate, + LightClientFinalityUpdate: altair.LightClientFinalityUpdate, + LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate, + LightClientStore: altair.LightClientStore, + BlindedBeaconBlock: bellatrix.BlindedBeaconBlock, + BlindedBeaconBlockBody: bellatrix.BlindedBeaconBlockBody, + SignedBlindedBeaconBlock: bellatrix.SignedBlindedBeaconBlock, + ExecutionPayload: bellatrix.ExecutionPayload, + ExecutionPayloadHeader: bellatrix.ExecutionPayloadHeader, + BuilderBid: bellatrix.BuilderBid, + SignedBuilderBid: bellatrix.SignedBuilderBid, + SSEPayloadAttributes: bellatrix.SSEPayloadAttributes, + }, + [ForkName.capella]: { + BeaconBlock: capella.BeaconBlock, + BeaconBlockBody: capella.BeaconBlockBody, + BeaconState: capella.BeaconState, + SignedBeaconBlock: capella.SignedBeaconBlock, + Metadata: altair.Metadata, + LightClientHeader: capella.LightClientHeader, + LightClientBootstrap: capella.LightClientBootstrap, + LightClientUpdate: capella.LightClientUpdate, + LightClientFinalityUpdate: capella.LightClientFinalityUpdate, + LightClientOptimisticUpdate: capella.LightClientOptimisticUpdate, + LightClientStore: capella.LightClientStore, + BlindedBeaconBlock: capella.BlindedBeaconBlock, + BlindedBeaconBlockBody: capella.BlindedBeaconBlockBody, + SignedBlindedBeaconBlock: capella.SignedBlindedBeaconBlock, + ExecutionPayload: capella.ExecutionPayload, + ExecutionPayloadHeader: capella.ExecutionPayloadHeader, + BuilderBid: capella.BuilderBid, + SignedBuilderBid: capella.SignedBuilderBid, + SSEPayloadAttributes: capella.SSEPayloadAttributes, + }, + [ForkName.deneb]: { + BeaconBlock: deneb.BeaconBlock, + BeaconBlockBody: deneb.BeaconBlockBody, + BeaconState: deneb.BeaconState, + SignedBeaconBlock: deneb.SignedBeaconBlock, + Metadata: altair.Metadata, + LightClientHeader: deneb.LightClientHeader, + LightClientBootstrap: deneb.LightClientBootstrap, + LightClientUpdate: deneb.LightClientUpdate, + LightClientFinalityUpdate: deneb.LightClientFinalityUpdate, + LightClientOptimisticUpdate: deneb.LightClientOptimisticUpdate, + LightClientStore: deneb.LightClientStore, + BlindedBeaconBlock: deneb.BlindedBeaconBlock, + BlindedBeaconBlockBody: deneb.BlindedBeaconBlockBody, + SignedBlindedBeaconBlock: deneb.SignedBlindedBeaconBlock, + ExecutionPayload: deneb.ExecutionPayload, + ExecutionPayloadHeader: deneb.ExecutionPayloadHeader, + BuilderBid: deneb.BuilderBid, + SignedBuilderBid: deneb.SignedBuilderBid, + SSEPayloadAttributes: deneb.SSEPayloadAttributes, + ExecutionPayloadAndBlobsBundle: deneb.ExecutionPayloadAndBlobsBundle, + }, +}; + +const pick = , K extends keyof T>(obj: T, ...keys: K[]): Pick => + Object.fromEntries(keys.filter((key) => key in obj).map((key) => [key, obj[key]])) as Pick; + +const executionForks: ForkExecution[] = [ForkName.bellatrix, ForkName.capella, ForkName.deneb]; +const lightCLientForks: ForkLightClient[] = [ForkName.altair, ForkName.bellatrix, ForkName.capella, ForkName.deneb]; +const blobsForks: ForkBlobs[] = [ForkName.deneb]; + +export const allForksExecution = pick(typesByFork, ...executionForks); +export const allForksLightClient = pick(typesByFork, ...lightCLientForks); +export const allForksBlobs = pick(typesByFork, ...blobsForks); +export const allForksBlinded = { + bellatrix: { + BeaconBlockBody: bellatrix.BlindedBeaconBlockBody, + BeaconBlock: bellatrix.BlindedBeaconBlock, + SignedBeaconBlock: bellatrix.SignedBlindedBeaconBlock, + }, + capella: { + BeaconBlockBody: capella.BlindedBeaconBlockBody, + BeaconBlock: capella.BlindedBeaconBlock, + SignedBeaconBlock: capella.SignedBlindedBeaconBlock, + }, + deneb: { + BeaconBlockBody: deneb.BlindedBeaconBlockBody, + BeaconBlock: deneb.BlindedBeaconBlock, + SignedBeaconBlock: deneb.SignedBlindedBeaconBlock, + }, +}; + +// TODO: These helpers should be removed along with `allForksBlinded` +type SSZBlindedTypesByFork = { + [F in keyof typeof allForksBlinded]: { + [T in keyof (typeof allForksBlinded)[F]]: (typeof allForksBlinded)[F][T]; + }; +}; + +// TODO: These helpers should be removed along with `allForksBlinded` +export type SSZBlindedTypesFor< + F extends ForkExecution, + K extends keyof SSZBlindedTypesByFork[F] | void = void, +> = K extends void + ? // It compiles fine, need to debug the error + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + {[K2 in keyof SSZBlindedTypesByFork[F]]: UnionSSZForksTypeOf} + : // It compiles fine, need to debug the error + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + UnionSSZForksTypeOf]>; + +/** + * A type of union of forks must accept as any parameter the UNION of all fork types. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type UnionSSZForksTypeOf> = CompositeType< + ValueOf, + CompositeView, + CompositeViewDU +>; + +type SSZTypesByFork = { + [F in keyof typeof typesByFork]: { + [T in keyof (typeof typesByFork)[F]]: (typeof typesByFork)[F][T]; + }; +}; + +export type SSZTypesFor = K extends void + ? // It compiles fine, need to debug the error + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + {[K2 in keyof SSZTypesByFork[F]]: UnionSSZForksTypeOf} + : // It compiles fine, need to debug the error + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + UnionSSZForksTypeOf]>; + +export function sszTypesFor( + fork: F, + typeName?: K +): SSZTypesFor { + return ( + typeName === undefined ? typesByFork[fork] : typesByFork[fork][typeName as keyof SSZTypesByFork[F]] + ) as SSZTypesFor; +} diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index e2e416fa3667..4f844884b88e 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -1,3 +1,9 @@ +import {ForkAll, ForkBlobs, ForkExecution, ForkLightClient, ForkName, ForkPreBlobs} from "@lodestar/params"; +import {ts as phase0} from "./phase0/index.js"; +import {ts as altair} from "./altair/index.js"; +import {ts as bellatrix} from "./bellatrix/index.js"; +import {ts as capella} from "./capella/index.js"; +import {ts as deneb} from "./deneb/index.js"; import {Slot} from "./primitive/types.js"; export * from "./primitive/types.js"; @@ -7,8 +13,6 @@ export {ts as bellatrix} from "./bellatrix/index.js"; export {ts as capella} from "./capella/index.js"; export {ts as deneb} from "./deneb/index.js"; -export {ts as allForks} from "./allForks/index.js"; - /** Common non-spec type to represent roots as strings */ export type RootHex = string; @@ -20,3 +24,172 @@ export enum ProducedBlockSource { export type SlotRootHex = {slot: Slot; root: RootHex}; export type SlotOptionalRoot = {slot: Slot; root?: RootHex}; + +type TypesByFork = { + [ForkName.phase0]: { + BeaconBlockHeader: phase0.BeaconBlockHeader; + SignedBeaconBlockHeader: phase0.SignedBeaconBlockHeader; + BeaconBlock: phase0.BeaconBlock; + BeaconBlockBody: phase0.BeaconBlockBody; + BeaconState: phase0.BeaconState; + SignedBeaconBlock: phase0.SignedBeaconBlock; + Metadata: phase0.Metadata; + }; + [ForkName.altair]: { + BeaconBlockHeader: phase0.BeaconBlockHeader; + SignedBeaconBlockHeader: phase0.SignedBeaconBlockHeader; + BeaconBlock: altair.BeaconBlock; + BeaconBlockBody: altair.BeaconBlockBody; + BeaconState: altair.BeaconState; + SignedBeaconBlock: altair.SignedBeaconBlock; + Metadata: altair.Metadata; + LightClientHeader: altair.LightClientHeader; + LightClientBootstrap: altair.LightClientBootstrap; + LightClientUpdate: altair.LightClientUpdate; + LightClientFinalityUpdate: altair.LightClientFinalityUpdate; + LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate; + LightClientStore: altair.LightClientStore; + SyncCommittee: altair.SyncCommittee; + SyncAggregate: altair.SyncAggregate; + }; + [ForkName.bellatrix]: { + BeaconBlockHeader: phase0.BeaconBlockHeader; + SignedBeaconBlockHeader: phase0.SignedBeaconBlockHeader; + BeaconBlock: bellatrix.BeaconBlock; + BeaconBlockBody: bellatrix.BeaconBlockBody; + BeaconState: bellatrix.BeaconState; + SignedBeaconBlock: bellatrix.SignedBeaconBlock; + Metadata: altair.Metadata; + LightClientHeader: altair.LightClientHeader; + LightClientBootstrap: altair.LightClientBootstrap; + LightClientUpdate: altair.LightClientUpdate; + LightClientFinalityUpdate: altair.LightClientFinalityUpdate; + LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate; + LightClientStore: altair.LightClientStore; + BlindedBeaconBlock: bellatrix.BlindedBeaconBlock; + BlindedBeaconBlockBody: bellatrix.BlindedBeaconBlockBody; + SignedBlindedBeaconBlock: bellatrix.SignedBlindedBeaconBlock; + ExecutionPayload: bellatrix.ExecutionPayload; + ExecutionPayloadHeader: bellatrix.ExecutionPayloadHeader; + BuilderBid: bellatrix.BuilderBid; + SignedBuilderBid: bellatrix.SignedBuilderBid; + SSEPayloadAttributes: bellatrix.SSEPayloadAttributes; + SyncCommittee: altair.SyncCommittee; + SyncAggregate: altair.SyncAggregate; + }; + [ForkName.capella]: { + BeaconBlockHeader: phase0.BeaconBlockHeader; + SignedBeaconBlockHeader: phase0.SignedBeaconBlockHeader; + BeaconBlock: capella.BeaconBlock; + BeaconBlockBody: capella.BeaconBlockBody; + BeaconState: capella.BeaconState; + SignedBeaconBlock: capella.SignedBeaconBlock; + Metadata: altair.Metadata; + LightClientHeader: capella.LightClientHeader; + LightClientBootstrap: capella.LightClientBootstrap; + LightClientUpdate: capella.LightClientUpdate; + LightClientFinalityUpdate: capella.LightClientFinalityUpdate; + LightClientOptimisticUpdate: capella.LightClientOptimisticUpdate; + LightClientStore: capella.LightClientStore; + BlindedBeaconBlock: capella.BlindedBeaconBlock; + BlindedBeaconBlockBody: capella.BlindedBeaconBlockBody; + SignedBlindedBeaconBlock: capella.SignedBlindedBeaconBlock; + ExecutionPayload: capella.ExecutionPayload; + ExecutionPayloadHeader: capella.ExecutionPayloadHeader; + BuilderBid: capella.BuilderBid; + SignedBuilderBid: capella.SignedBuilderBid; + SSEPayloadAttributes: capella.SSEPayloadAttributes; + SyncCommittee: altair.SyncCommittee; + SyncAggregate: altair.SyncAggregate; + }; + [ForkName.deneb]: { + BeaconBlockHeader: phase0.BeaconBlockHeader; + SignedBeaconBlockHeader: phase0.SignedBeaconBlockHeader; + BeaconBlock: deneb.BeaconBlock; + BeaconBlockBody: deneb.BeaconBlockBody; + BeaconState: deneb.BeaconState; + SignedBeaconBlock: deneb.SignedBeaconBlock; + Metadata: altair.Metadata; + LightClientHeader: deneb.LightClientHeader; + LightClientBootstrap: deneb.LightClientBootstrap; + LightClientUpdate: deneb.LightClientUpdate; + LightClientFinalityUpdate: deneb.LightClientFinalityUpdate; + LightClientOptimisticUpdate: deneb.LightClientOptimisticUpdate; + LightClientStore: deneb.LightClientStore; + BlindedBeaconBlock: deneb.BlindedBeaconBlock; + BlindedBeaconBlockBody: deneb.BlindedBeaconBlockBody; + SignedBlindedBeaconBlock: deneb.SignedBlindedBeaconBlock; + ExecutionPayload: deneb.ExecutionPayload; + ExecutionPayloadHeader: deneb.ExecutionPayloadHeader; + BuilderBid: deneb.BuilderBid; + SignedBuilderBid: deneb.SignedBuilderBid; + SSEPayloadAttributes: deneb.SSEPayloadAttributes; + BlockContents: {block: BeaconBlock; kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs}; + SignedBlockContents: { + signedBlock: SignedBeaconBlock; + kzgProofs: deneb.KZGProofs; + blobs: deneb.Blobs; + }; + ExecutionPayloadAndBlobsBundle: deneb.ExecutionPayloadAndBlobsBundle; + BlobsBundle: deneb.BlobsBundle; + Contents: deneb.Contents; + SyncCommittee: altair.SyncCommittee; + SyncAggregate: altair.SyncAggregate; + }; +}; + +export type TypesFor = K extends void + ? TypesByFork[F] + : TypesByFork[F][Exclude]; + +export type BeaconBlockHeader = TypesByFork[F]["BeaconBlockHeader"]; +export type SignedBeaconBlockHeader = TypesByFork[F]["SignedBeaconBlockHeader"]; + +export type BeaconBlock = TypesByFork[F]["BeaconBlock"]; +export type BlindedBeaconBlock = TypesByFork[F]["BlindedBeaconBlock"]; + +export type SignedBeaconBlock = TypesByFork[F]["SignedBeaconBlock"]; +export type SignedBlindedBeaconBlock = + TypesByFork[F]["SignedBlindedBeaconBlock"]; + +export type BeaconBlockBody = TypesByFork[F]["BeaconBlockBody"]; +export type BlindedBeaconBlockBody = TypesByFork[F]["BlindedBeaconBlockBody"]; + +export type BlockContents = TypesByFork[F]["BlockContents"]; +export type SignedBlockContents = TypesByFork[F]["SignedBlockContents"]; +export type SignedOrUnsignedBlockContents = BlockContents | SignedBlockContents; + +export type BeaconBlockOrContents = + | BeaconBlock + | BlockContents; + +export type SignedBeaconBlockOrContents = + | SignedBeaconBlock + | SignedBlockContents; + +export type ExecutionPayload = TypesByFork[F]["ExecutionPayload"]; +export type ExecutionPayloadHeader = TypesByFork[F]["ExecutionPayloadHeader"]; + +export type BlobsBundle = TypesByFork[F]["BlobsBundle"]; +export type Contents = TypesByFork[F]["Contents"]; +export type ExecutionPayloadAndBlobsBundle = + TypesByFork[F]["ExecutionPayloadAndBlobsBundle"]; + +export type LightClientHeader = TypesByFork[F]["LightClientHeader"]; +export type LightClientBootstrap = TypesByFork[F]["LightClientBootstrap"]; +export type LightClientUpdate = TypesByFork[F]["LightClientUpdate"]; +export type LightClientFinalityUpdate = + TypesByFork[F]["LightClientFinalityUpdate"]; +export type LightClientOptimisticUpdate = + TypesByFork[F]["LightClientOptimisticUpdate"]; +export type LightClientStore = TypesByFork[F]["LightClientStore"]; +export type SyncCommittee = TypesByFork[F]["SyncCommittee"]; +export type SyncAggregate = TypesByFork[F]["SyncAggregate"]; + +export type BeaconState = TypesByFork[F]["BeaconState"]; + +export type Metadata = TypesByFork[F]["Metadata"]; + +export type BuilderBid = TypesByFork[F]["BuilderBid"]; +export type SignedBuilderBid = TypesByFork[F]["SignedBuilderBid"]; +export type SSEPayloadAttributes = TypesByFork[F]["SSEPayloadAttributes"]; diff --git a/packages/types/src/utils/typeguards.ts b/packages/types/src/utils/typeguards.ts index 781738c3dbad..7848be700b00 100644 --- a/packages/types/src/utils/typeguards.ts +++ b/packages/types/src/utils/typeguards.ts @@ -1,51 +1,68 @@ +import {ForkBlobs, ForkExecution} from "@lodestar/params"; import { - FullOrBlindedBeaconBlockOrContents, - FullOrBlindedBeaconBlock, - FullOrBlindedSignedBeaconBlock, - FullOrBlindedBeaconBlockBody, - FullOrBlindedExecutionPayload, - ExecutionPayloadHeader, - BlindedBeaconBlockBody, - BlindedBeaconBlock, BlockContents, - SignedBlindedBeaconBlock, - SignedBlockContents, SignedBeaconBlock, ExecutionPayload, ExecutionPayloadAndBlobsBundle, -} from "../allForks/types.js"; + BeaconBlockBody, + BeaconBlockOrContents, + SignedBeaconBlockOrContents, + ExecutionPayloadHeader, + BlindedBeaconBlock, + SignedBlindedBeaconBlock, + BlindedBeaconBlockBody, + SignedBlockContents, + BeaconBlock, +} from "../types.js"; -export function isBlindedExecution(payload: FullOrBlindedExecutionPayload): payload is ExecutionPayloadHeader { - // we just check transactionsRoot for determinging as it the base field +export function isExecutionPayload( + payload: ExecutionPayload | ExecutionPayloadHeader +): payload is ExecutionPayload { + // we just check transactionsRoot for determining as it the base field // that is present and differs from ExecutionPayload for all forks - return (payload as ExecutionPayloadHeader).transactionsRoot !== undefined; + return (payload as ExecutionPayload).transactions !== undefined; } -export function isBlindedBeaconBlock(block: FullOrBlindedBeaconBlockOrContents): block is BlindedBeaconBlock { - const body = (block as FullOrBlindedBeaconBlock).body; - return body !== undefined && isBlindedBeaconBlockBody(body); +export function isBlindedExecutionPayload( + payload: ExecutionPayload | ExecutionPayloadHeader +): payload is ExecutionPayloadHeader { + // we just check transactionsRoot for determining as it the base field + // that is present and differs from ExecutionPayload for all forks + return (payload as ExecutionPayloadHeader).transactionsRoot !== undefined; } -export function isBlindedBeaconBlockBody(body: FullOrBlindedBeaconBlockBody): body is BlindedBeaconBlockBody { - return (body as BlindedBeaconBlockBody).executionPayloadHeader !== undefined; +export function isExecutionPayloadAndBlobsBundle( + data: ExecutionPayload | ExecutionPayloadAndBlobsBundle +): data is ExecutionPayloadAndBlobsBundle { + return (data as ExecutionPayloadAndBlobsBundle).blobsBundle !== undefined; } -export function isBlindedSignedBeaconBlock( - signedBlock: FullOrBlindedSignedBeaconBlock -): signedBlock is SignedBlindedBeaconBlock { - return (signedBlock as SignedBlindedBeaconBlock).message.body.executionPayloadHeader !== undefined; +export function isBlindedBeaconBlock( + block: BeaconBlockOrContents | SignedBeaconBlockOrContents +): block is BlindedBeaconBlock { + return (block as BeaconBlock).body !== null && isBlindedBeaconBlockBody((block as BeaconBlock).body); } -export function isBlockContents(data: FullOrBlindedBeaconBlockOrContents): data is BlockContents { - return (data as BlockContents).kzgProofs !== undefined; +export function isBlindedSignedBeaconBlock( + signedBlock: SignedBeaconBlock | SignedBeaconBlockOrContents +): signedBlock is SignedBlindedBeaconBlock { + return (signedBlock as SignedBlindedBeaconBlock).message.body.executionPayloadHeader !== undefined; +} + +export function isBlindedBeaconBlockBody( + body: BeaconBlockBody | BlindedBeaconBlockBody +): body is BlindedBeaconBlockBody { + return (body as BlindedBeaconBlockBody).executionPayloadHeader !== undefined; } -export function isSignedBlockContents(data: SignedBeaconBlock | SignedBlockContents): data is SignedBlockContents { - return (data as SignedBlockContents).kzgProofs !== undefined; +export function isBlockContents( + data: BeaconBlockOrContents | SignedBeaconBlockOrContents +): data is BlockContents { + return (data as BlockContents).kzgProofs !== undefined; } -export function isExecutionPayloadAndBlobsBundle( - data: ExecutionPayload | ExecutionPayloadAndBlobsBundle -): data is ExecutionPayloadAndBlobsBundle { - return (data as ExecutionPayloadAndBlobsBundle).blobsBundle !== undefined; +export function isSignedBlockContents( + data: SignedBeaconBlockOrContents | BeaconBlockOrContents +): data is SignedBlockContents { + return (data as SignedBlockContents).kzgProofs !== undefined; } diff --git a/packages/validator/src/services/block.ts b/packages/validator/src/services/block.ts index ab855e264447..661ff47440db 100644 --- a/packages/validator/src/services/block.ts +++ b/packages/validator/src/services/block.ts @@ -3,11 +3,15 @@ import { BLSPubkey, Slot, BLSSignature, - allForks, - isBlindedSignedBeaconBlock, ProducedBlockSource, deneb, isBlockContents, + BeaconBlock, + BeaconBlockOrContents, + isBlindedSignedBeaconBlock, + BlindedBeaconBlock, + SignedBeaconBlock, + SignedBlindedBeaconBlock, } from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {ForkPreBlobs, ForkBlobs, ForkSeq, ForkExecution, ForkName} from "@lodestar/params"; @@ -26,14 +30,14 @@ import {BlockDutiesService, GENESIS_SLOT} from "./blockDuties.js"; type FullOrBlindedBlockWithContents = | { version: ForkPreBlobs; - block: allForks.BeaconBlock; + block: BeaconBlock; contents: null; executionPayloadBlinded: false; executionPayloadSource: ProducedBlockSource.engine; } | { version: ForkBlobs; - block: allForks.BeaconBlock; + block: BeaconBlock; contents: { kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs; @@ -43,7 +47,7 @@ type FullOrBlindedBlockWithContents = } | { version: ForkExecution; - block: allForks.BlindedBeaconBlock; + block: BlindedBeaconBlock; contents: null; executionPayloadBlinded: true; executionPayloadSource: ProducedBlockSource; @@ -174,7 +178,7 @@ export class BlockProposingService { } private publishBlockWrapper = async ( - signedBlock: allForks.FullOrBlindedSignedBeaconBlock, + signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock, contents: {kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs} | null, opts: {broadcastValidation?: routes.beacon.BroadcastValidation} = {} ): Promise => { @@ -189,7 +193,12 @@ export class BlockProposingService { if (contents === null) { (await this.api.beacon.publishBlockV2({signedBlockOrContents: signedBlock, ...opts})).assertOk(); } else { - (await this.api.beacon.publishBlockV2({signedBlockOrContents: {...contents, signedBlock}, ...opts})).assertOk(); + ( + await this.api.beacon.publishBlockV2({ + signedBlockOrContents: {...contents, signedBlock: signedBlock as SignedBeaconBlock}, + ...opts, + }) + ).assertOk(); } } }; @@ -273,7 +282,7 @@ export class BlockProposingService { } function parseProduceBlockResponse( - response: {data: allForks.FullOrBlindedBeaconBlockOrContents} & { + response: {data: BeaconBlockOrContents | BlindedBeaconBlock} & { executionPayloadSource: ProducedBlockSource; executionPayloadBlinded: boolean; version: ForkName; @@ -304,10 +313,11 @@ function parseProduceBlockResponse( debugLogCtx, } as FullOrBlindedBlockWithContents & DebugLogCtx; } else { - if (isBlockContents(response.data)) { + const data = response.data; + if (isBlockContents(data)) { return { - block: response.data.block, - contents: {blobs: response.data.blobs, kzgProofs: response.data.kzgProofs}, + block: data.block, + contents: {blobs: data.blobs, kzgProofs: data.kzgProofs}, version: response.version, executionPayloadBlinded: false, executionPayloadSource, diff --git a/packages/validator/src/services/validatorStore.ts b/packages/validator/src/services/validatorStore.ts index 6cd9ed8dc065..ef675ff4a001 100644 --- a/packages/validator/src/services/validatorStore.ts +++ b/packages/validator/src/services/validatorStore.ts @@ -21,14 +21,17 @@ import { DOMAIN_APPLICATION_BUILDER, } from "@lodestar/params"; import { - allForks, altair, + BeaconBlock, bellatrix, + BlindedBeaconBlock, BLSPubkey, BLSSignature, Epoch, phase0, Root, + SignedBeaconBlock, + SignedBlindedBeaconBlock, Slot, ssz, ValidatorIndex, @@ -430,10 +433,10 @@ export class ValidatorStore { async signBlock( pubkey: BLSPubkey, - blindedOrFull: allForks.FullOrBlindedBeaconBlock, + blindedOrFull: BeaconBlock | BlindedBeaconBlock, currentSlot: Slot, logger?: LoggerVc - ): Promise { + ): Promise { // Make sure the block slot is not higher than the current slot to avoid potential attacks. if (blindedOrFull.slot > currentSlot) { throw Error(`Not signing block with slot ${blindedOrFull.slot} greater than current slot ${currentSlot}`); @@ -469,7 +472,7 @@ export class ValidatorStore { return { message: blindedOrFull, signature: await this.getSignature(pubkey, signingRoot, signingSlot, signableMessage), - } as allForks.FullOrBlindedSignedBeaconBlock; + } as SignedBeaconBlock | SignedBlindedBeaconBlock; } async signRandao(pubkey: BLSPubkey, slot: Slot): Promise { diff --git a/packages/validator/src/util/externalSignerClient.ts b/packages/validator/src/util/externalSignerClient.ts index 374da79b7989..dc1d0d0f1dd5 100644 --- a/packages/validator/src/util/externalSignerClient.ts +++ b/packages/validator/src/util/externalSignerClient.ts @@ -1,11 +1,11 @@ import {ContainerType, toHexString, ValueOf} from "@chainsafe/ssz"; import {fetch} from "@lodestar/api"; -import {phase0, altair, capella} from "@lodestar/types"; +import {phase0, altair, capella, BeaconBlock, BlindedBeaconBlock} from "@lodestar/types"; import {ForkSeq} from "@lodestar/params"; import {ValidatorRegistrationV1} from "@lodestar/types/bellatrix"; import {BeaconConfig} from "@lodestar/config"; import {computeEpochAtSlot, blindedOrFullBlockToHeader} from "@lodestar/state-transition"; -import {allForks, Epoch, Root, RootHex, Slot, ssz} from "@lodestar/types"; +import {Epoch, Root, RootHex, Slot, ssz} from "@lodestar/types"; import {PubkeyHex} from "../types.js"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -63,7 +63,7 @@ export type SignableMessage = | {type: SignableMessageType.AGGREGATION_SLOT; data: {slot: Slot}} | {type: SignableMessageType.AGGREGATE_AND_PROOF; data: phase0.AggregateAndProof} | {type: SignableMessageType.ATTESTATION; data: phase0.AttestationData} - | {type: SignableMessageType.BLOCK_V2; data: allForks.FullOrBlindedBeaconBlock} + | {type: SignableMessageType.BLOCK_V2; data: BeaconBlock | BlindedBeaconBlock} | {type: SignableMessageType.DEPOSIT; data: ValueOf} | {type: SignableMessageType.RANDAO_REVEAL; data: {epoch: Epoch}} | {type: SignableMessageType.VOLUNTARY_EXIT; data: phase0.VoluntaryExit} From 3cc1cb40261d2a38df79f705b5a9a2cac0279505 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Mon, 24 Jun 2024 12:47:21 +0200 Subject: [PATCH 32/55] chore: cleanup usage of blinded types (#6903) * Cleanup blinded types * Fix lint error --- .../api/src/beacon/routes/beacon/block.ts | 18 ++++----- packages/api/src/beacon/routes/validator.ts | 6 +-- packages/api/src/builder/routes.ts | 6 +-- packages/api/src/utils/fork.ts | 9 +---- .../src/api/impl/beacon/blocks/index.ts | 4 +- .../src/api/impl/validator/index.ts | 2 +- packages/beacon-node/src/chain/chain.ts | 6 +-- packages/config/src/forkConfig/index.ts | 7 ---- packages/config/src/forkConfig/types.ts | 4 +- .../src/signatureSets/proposer.ts | 2 +- .../state-transition/src/util/blindedBlock.ts | 4 +- packages/types/src/index.ts | 4 +- packages/types/src/sszTypes.ts | 38 ------------------- 13 files changed, 28 insertions(+), 82 deletions(-) diff --git a/packages/api/src/beacon/routes/beacon/block.ts b/packages/api/src/beacon/routes/beacon/block.ts index dcf0d07c7a9b..facd3da55059 100644 --- a/packages/api/src/beacon/routes/beacon/block.ts +++ b/packages/api/src/beacon/routes/beacon/block.ts @@ -22,7 +22,7 @@ import { ExecutionOptimisticFinalizedAndVersionMeta, MetaHeader, } from "../../../utils/metadata.js"; -import {getBlindedForkTypes, toForkName} from "../../../utils/fork.js"; +import {getExecutionForkTypes, toForkName} from "../../../utils/fork.js"; import {fromHeaders} from "../../../utils/headers.js"; import {WireFormat} from "../../../utils/wireFormat.js"; @@ -414,7 +414,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = config.getForkName(signedBlindedBlock.message.slot); return { - body: getBlindedForkTypes(fork).SignedBeaconBlock.toJson(signedBlindedBlock), + body: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.toJson(signedBlindedBlock), headers: { [MetaHeader.Version]: fork, }, @@ -423,13 +423,13 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); return { - signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.fromJson(body), + signedBlindedBlock: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.fromJson(body), }; }, writeReqSsz: ({signedBlindedBlock}) => { const fork = config.getForkName(signedBlindedBlock.message.slot); return { - body: getBlindedForkTypes(fork).SignedBeaconBlock.serialize(signedBlindedBlock), + body: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.serialize(signedBlindedBlock), headers: { [MetaHeader.Version]: fork, }, @@ -438,7 +438,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); return { - signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.deserialize(body), + signedBlindedBlock: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.deserialize(body), }; }, schema: { @@ -458,7 +458,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = config.getForkName(signedBlindedBlock.message.slot); return { - body: getBlindedForkTypes(fork).SignedBeaconBlock.toJson(signedBlindedBlock), + body: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.toJson(signedBlindedBlock), headers: { [MetaHeader.Version]: fork, @@ -469,14 +469,14 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); return { - signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.fromJson(body), + signedBlindedBlock: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.fromJson(body), broadcastValidation: query.broadcast_validation as BroadcastValidation, }; }, writeReqSsz: ({signedBlindedBlock, broadcastValidation}) => { const fork = config.getForkName(signedBlindedBlock.message.slot); return { - body: getBlindedForkTypes(fork).SignedBeaconBlock.serialize(signedBlindedBlock), + body: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.serialize(signedBlindedBlock), headers: { [MetaHeader.Version]: fork, }, @@ -486,7 +486,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); return { - signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.deserialize(body), + signedBlindedBlock: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.deserialize(body), broadcastValidation: query.broadcast_validation as BroadcastValidation, }; }, diff --git a/packages/api/src/beacon/routes/validator.ts b/packages/api/src/beacon/routes/validator.ts index cd26f8997764..32a76a536d81 100644 --- a/packages/api/src/beacon/routes/validator.ts +++ b/packages/api/src/beacon/routes/validator.ts @@ -20,7 +20,7 @@ import { } from "@lodestar/types"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; import {fromGraffitiHex, toBoolean, toGraffitiHex} from "../../utils/serdes.js"; -import {getBlindedForkTypes, toForkName} from "../../utils/fork.js"; +import {getExecutionForkTypes, toForkName} from "../../utils/fork.js"; import { ArrayOf, EmptyMeta, @@ -685,7 +685,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions (executionPayloadBlinded - ? getBlindedForkTypes(version).BeaconBlock + ? getExecutionForkTypes(version).BlindedBeaconBlock : isForkBlobs(version) ? BlockContentsType : ssz[version].BeaconBlock) as Type @@ -753,7 +753,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions getBlindedForkTypes(fork).BeaconBlock), + data: WithVersion((fork) => getExecutionForkTypes(fork).BlindedBeaconBlock), meta: VersionCodec, }, }, diff --git a/packages/api/src/builder/routes.ts b/packages/api/src/builder/routes.ts index 7e6a6e24b1b7..971f4a1e63fe 100644 --- a/packages/api/src/builder/routes.ts +++ b/packages/api/src/builder/routes.ts @@ -27,7 +27,7 @@ import { JsonOnlyReq, WithVersion, } from "../utils/codecs.js"; -import {getBlindedForkTypes, getExecutionForkTypes, toForkName} from "../utils/fork.js"; +import {getExecutionForkTypes, toForkName} from "../utils/fork.js"; import {fromHeaders} from "../utils/headers.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -130,7 +130,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = config.getForkName(signedBlindedBlock.message.slot); return { - body: getBlindedForkTypes(fork).SignedBeaconBlock.toJson(signedBlindedBlock), + body: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.toJson(signedBlindedBlock), headers: { [MetaHeader.Version]: fork, }, @@ -139,7 +139,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); return { - signedBlindedBlock: getBlindedForkTypes(fork).SignedBeaconBlock.fromJson(body), + signedBlindedBlock: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.fromJson(body), }; }, schema: { diff --git a/packages/api/src/utils/fork.ts b/packages/api/src/utils/fork.ts index 0925e750719a..5854a0867d02 100644 --- a/packages/api/src/utils/fork.ts +++ b/packages/api/src/utils/fork.ts @@ -7,7 +7,7 @@ import { isForkExecution, isForkLightClient, } from "@lodestar/params"; -import {SSZBlindedTypesFor, SSZTypesFor, ssz, sszTypesFor} from "@lodestar/types"; +import {SSZTypesFor, sszTypesFor} from "@lodestar/types"; export function toForkName(version: string): ForkName { // Teku returns fork as UPPERCASE @@ -35,13 +35,6 @@ export function getExecutionForkTypes(fork: ForkName): SSZTypesFor { - if (!isForkExecution(fork)) { - throw Error(`Invalid fork=${fork} for blinded fork types`); - } - return ssz.allForksBlinded[fork]; -} - export function getBlobsForkTypes(fork: ForkName): SSZTypesFor { if (!isForkBlobs(fork)) { throw Error(`Invalid fork=${fork} for blobs fork types`); 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 d4675de21185..274d88e5d508 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -240,8 +240,8 @@ export function getBeaconBlockApi({ const slot = signedBlindedBlock.message.slot; const blockRoot = toHex( chain.config - .getBlindedForkTypes(signedBlindedBlock.message.slot) - .BeaconBlock.hashTreeRoot(signedBlindedBlock.message) + .getExecutionForkTypes(signedBlindedBlock.message.slot) + .BlindedBeaconBlock.hashTreeRoot(signedBlindedBlock.message) ); // Either the payload/blobs are cached from i) engine locally or ii) they are from the builder diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 8fb40e4ecb6b..486fc6e8062f 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -416,7 +416,7 @@ export function getValidatorApi({ slot, executionPayloadValue, consensusBlockValue, - root: toHex(config.getBlindedForkTypes(slot).BeaconBlock.hashTreeRoot(block)), + root: toHex(config.getExecutionForkTypes(slot).BlindedBeaconBlock.hashTreeRoot(block)), }); if (chain.opts.persistProducedBlocks) { diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index ccd10f7b4d6f..a6912d952b68 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -620,7 +620,7 @@ export class BeaconChain implements IBeaconChain { const bodyRoot = blockType === BlockType.Full ? this.config.getForkTypes(slot).BeaconBlockBody.hashTreeRoot(body) - : this.config.getBlindedForkTypes(slot).BeaconBlockBody.hashTreeRoot(body as BlindedBeaconBlockBody); + : this.config.getExecutionForkTypes(slot).BlindedBeaconBlockBody.hashTreeRoot(body as BlindedBeaconBlockBody); this.logger.debug("Computing block post state from the produced body", { slot, bodyRoot: toHexString(bodyRoot), @@ -640,7 +640,7 @@ export class BeaconChain implements IBeaconChain { const blockRoot = blockType === BlockType.Full ? this.config.getForkTypes(slot).BeaconBlock.hashTreeRoot(block) - : this.config.getBlindedForkTypes(slot).BeaconBlock.hashTreeRoot(block as BlindedBeaconBlock); + : this.config.getExecutionForkTypes(slot).BlindedBeaconBlock.hashTreeRoot(block as BlindedBeaconBlock); const blockRootHex = toHex(blockRoot); // track the produced block for consensus broadcast validations @@ -777,7 +777,7 @@ export class BeaconChain implements IBeaconChain { persistBlock(data: BeaconBlock | BlindedBeaconBlock, suffix?: string): void { const slot = data.slot; if (isBlindedBeaconBlock(data)) { - const sszType = this.config.getBlindedForkTypes(slot).BeaconBlock; + const sszType = this.config.getExecutionForkTypes(slot).BlindedBeaconBlock; void this.persistSszObject("BlindedBeaconBlock", sszType.serialize(data), sszType.hashTreeRoot(data), suffix); } else { const sszType = this.config.getForkTypes(slot).BeaconBlock; diff --git a/packages/config/src/forkConfig/index.ts b/packages/config/src/forkConfig/index.ts index 45213d0a611d..358c2d752001 100644 --- a/packages/config/src/forkConfig/index.ts +++ b/packages/config/src/forkConfig/index.ts @@ -101,13 +101,6 @@ export function createForkConfig(config: ChainConfig): ForkConfig { } return ssz.allForksExecution[forkName]; }, - getBlindedForkTypes(slot: Slot): (typeof ssz.allForksBlinded)[ForkExecution] { - const forkName = this.getForkName(slot); - if (!isForkExecution(forkName)) { - throw Error(`Invalid slot=${slot} fork=${forkName} for blinded fork types`); - } - return ssz.allForksBlinded[forkName]; - }, getLightClientForkTypes(slot: Slot): SSZTypesFor { const forkName = this.getForkName(slot); if (!isForkLightClient(forkName)) { diff --git a/packages/config/src/forkConfig/types.ts b/packages/config/src/forkConfig/types.ts index 93f34a62bc83..2905e6f03c34 100644 --- a/packages/config/src/forkConfig/types.ts +++ b/packages/config/src/forkConfig/types.ts @@ -1,5 +1,5 @@ import {ForkAll, ForkBlobs, ForkExecution, ForkLightClient, ForkName, ForkSeq} from "@lodestar/params"; -import {Epoch, SSZBlindedTypesFor, SSZTypesFor, Slot, Version} from "@lodestar/types"; +import {Epoch, SSZTypesFor, Slot, Version} from "@lodestar/types"; export type ForkInfo = { name: ForkName; @@ -34,8 +34,6 @@ export type ForkConfig = { getLightClientForkTypes(slot: Slot): SSZTypesFor; /** Get execution SSZ types by hard-fork*/ getExecutionForkTypes(slot: Slot): SSZTypesFor; - /** Get blinded SSZ types by hard-fork */ - getBlindedForkTypes(slot: Slot): SSZBlindedTypesFor; /** Get blobs SSZ types by hard-fork*/ getBlobsForkTypes(slot: Slot): SSZTypesFor; }; diff --git a/packages/state-transition/src/signatureSets/proposer.ts b/packages/state-transition/src/signatureSets/proposer.ts index b5e501bd16c7..e5ae7fd1f6f1 100644 --- a/packages/state-transition/src/signatureSets/proposer.ts +++ b/packages/state-transition/src/signatureSets/proposer.ts @@ -20,7 +20,7 @@ export function getBlockProposerSignatureSet( const domain = config.getDomain(state.slot, DOMAIN_BEACON_PROPOSER, signedBlock.message.slot); const blockType = isBlindedBeaconBlock(signedBlock.message) - ? config.getBlindedForkTypes(signedBlock.message.slot).BeaconBlock + ? config.getExecutionForkTypes(signedBlock.message.slot).BlindedBeaconBlock : config.getForkTypes(signedBlock.message.slot).BeaconBlock; return { diff --git a/packages/state-transition/src/util/blindedBlock.ts b/packages/state-transition/src/util/blindedBlock.ts index 2b9510bd1e99..d63b40a9fe88 100644 --- a/packages/state-transition/src/util/blindedBlock.ts +++ b/packages/state-transition/src/util/blindedBlock.ts @@ -25,7 +25,7 @@ export function blindedOrFullBlockHashTreeRoot( ): Root { return isBlindedBeaconBlock(blindedOrFull) ? // Blinded - config.getBlindedForkTypes(blindedOrFull.slot).BeaconBlock.hashTreeRoot(blindedOrFull) + config.getExecutionForkTypes(blindedOrFull.slot).BlindedBeaconBlock.hashTreeRoot(blindedOrFull) : // Full config.getForkTypes(blindedOrFull.slot).BeaconBlock.hashTreeRoot(blindedOrFull); } @@ -36,7 +36,7 @@ export function blindedOrFullBlockToHeader( ): BeaconBlockHeader { const bodyRoot = isBlindedBeaconBlock(blindedOrFull) ? // Blinded - config.getBlindedForkTypes(blindedOrFull.slot).BeaconBlockBody.hashTreeRoot(blindedOrFull.body) + config.getExecutionForkTypes(blindedOrFull.slot).BlindedBeaconBlockBody.hashTreeRoot(blindedOrFull.body) : // Full config.getForkTypes(blindedOrFull.slot).BeaconBlockBody.hashTreeRoot(blindedOrFull.body); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 7838a02d06e2..e0745834c7d1 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,8 +1,8 @@ export * from "./types.js"; import * as ssz from "./sszTypes.js"; import {sszTypesFor} from "./sszTypes.js"; -import type {SSZBlindedTypesFor, SSZTypesFor} from "./sszTypes.js"; -export {sszTypesFor, SSZBlindedTypesFor, SSZTypesFor, ssz}; +import type {SSZTypesFor} from "./sszTypes.js"; +export {sszTypesFor, SSZTypesFor, ssz}; // Typeguards export * from "./utils/typeguards.js"; // String type diff --git a/packages/types/src/sszTypes.ts b/packages/types/src/sszTypes.ts index 1ee5b9340630..eae1bbcdeaba 100644 --- a/packages/types/src/sszTypes.ts +++ b/packages/types/src/sszTypes.ts @@ -110,44 +110,6 @@ const blobsForks: ForkBlobs[] = [ForkName.deneb]; export const allForksExecution = pick(typesByFork, ...executionForks); export const allForksLightClient = pick(typesByFork, ...lightCLientForks); export const allForksBlobs = pick(typesByFork, ...blobsForks); -export const allForksBlinded = { - bellatrix: { - BeaconBlockBody: bellatrix.BlindedBeaconBlockBody, - BeaconBlock: bellatrix.BlindedBeaconBlock, - SignedBeaconBlock: bellatrix.SignedBlindedBeaconBlock, - }, - capella: { - BeaconBlockBody: capella.BlindedBeaconBlockBody, - BeaconBlock: capella.BlindedBeaconBlock, - SignedBeaconBlock: capella.SignedBlindedBeaconBlock, - }, - deneb: { - BeaconBlockBody: deneb.BlindedBeaconBlockBody, - BeaconBlock: deneb.BlindedBeaconBlock, - SignedBeaconBlock: deneb.SignedBlindedBeaconBlock, - }, -}; - -// TODO: These helpers should be removed along with `allForksBlinded` -type SSZBlindedTypesByFork = { - [F in keyof typeof allForksBlinded]: { - [T in keyof (typeof allForksBlinded)[F]]: (typeof allForksBlinded)[F][T]; - }; -}; - -// TODO: These helpers should be removed along with `allForksBlinded` -export type SSZBlindedTypesFor< - F extends ForkExecution, - K extends keyof SSZBlindedTypesByFork[F] | void = void, -> = K extends void - ? // It compiles fine, need to debug the error - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - {[K2 in keyof SSZBlindedTypesByFork[F]]: UnionSSZForksTypeOf} - : // It compiles fine, need to debug the error - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - UnionSSZForksTypeOf]>; /** * A type of union of forks must accept as any parameter the UNION of all fork types. From f20ec4efe8c5e6d37965d6ab51c4dea6d9007e22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 09:53:22 -0400 Subject: [PATCH 33/55] chore(deps): bump ws from 7.5.9 to 7.5.10 in /docs (#6896) Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10) --- updated-dependencies: - dependency-name: ws dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/yarn.lock | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/yarn.lock b/docs/yarn.lock index ad6053d02f35..a2c90a3ace6c 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2512,7 +2512,7 @@ "@docusaurus/theme-search-algolia" "3.4.0" "@docusaurus/types" "3.4.0" -"@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": +"@docusaurus/react-loadable@5.5.2": version "5.5.2" resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== @@ -9359,6 +9359,14 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" +"react-loadable@npm:@docusaurus/react-loadable@5.5.2": + version "5.5.2" + resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz#81aae0db81ecafbdaee3651f12804580868fa6ce" + integrity sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ== + dependencies: + "@types/react" "*" + prop-types "^15.6.2" + "react-loadable@npm:@docusaurus/react-loadable@6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz#de6c7f73c96542bd70786b8e522d535d69069dc4" @@ -10853,14 +10861,14 @@ write-file-atomic@^3.0.3: typedarray-to-buffer "^3.1.5" ws@^7.3.1: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.13.0: - version "8.16.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" - integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== xdg-basedir@^5.0.1, xdg-basedir@^5.1.0: version "5.1.0" From b453b37d53a946b6faf73277fe1d75becff3ae8f Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 24 Jun 2024 15:52:24 +0100 Subject: [PATCH 34/55] feat: add endpoint to fetch blinded blocks (#6905) * feat: add endpoint to fetch blinded blocks * Reorder test assertions --- .../api/src/beacon/routes/beacon/block.ts | 25 ++++- .../api/test/unit/beacon/oapiSpec.test.ts | 1 - .../api/test/unit/beacon/testData/beacon.ts | 7 ++ .../src/api/impl/beacon/blocks/index.ts | 24 ++++- .../api/impl/beacon/block/endpoint.test.ts | 101 ++++++++++++++++++ .../state-transition/src/util/blindedBlock.ts | 12 ++- 6 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 packages/beacon-node/test/e2e/api/impl/beacon/block/endpoint.test.ts diff --git a/packages/api/src/beacon/routes/beacon/block.ts b/packages/api/src/beacon/routes/beacon/block.ts index facd3da55059..cbf206e51a4a 100644 --- a/packages/api/src/beacon/routes/beacon/block.ts +++ b/packages/api/src/beacon/routes/beacon/block.ts @@ -12,7 +12,7 @@ import { SignedBeaconBlockOrContents, SignedBlindedBeaconBlock, } from "@lodestar/types"; -import {ForkName, ForkSeq} from "@lodestar/params"; +import {ForkName, ForkPreExecution, ForkSeq, isForkExecution} from "@lodestar/params"; import {Endpoint, RequestCodec, RouteDefinitions, Schema} from "../../../utils/index.js"; import {EmptyMeta, EmptyResponseCodec, EmptyResponseData, WithVersion} from "../../../utils/codecs.js"; import { @@ -88,6 +88,18 @@ export type Endpoints = { ExecutionOptimisticFinalizedAndVersionMeta >; + /** + * Get blinded block + * Retrieves blinded block for given block id. + */ + getBlindedBlock: Endpoint< + "GET", + BlockArgs, + {params: {block_id: string}}, + SignedBlindedBeaconBlock | SignedBeaconBlock, + ExecutionOptimisticFinalizedAndVersionMeta + >; + /** * Get block attestations * Retrieves attestation included in requested block. @@ -226,6 +238,17 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions + isForkExecution(fork) ? ssz[fork].SignedBlindedBeaconBlock : ssz[fork].SignedBeaconBlock + ), + meta: ExecutionOptimisticFinalizedAndVersionCodec, + }, + }, getBlockAttestations: { url: "/eth/v1/beacon/blocks/{block_id}/attestations", method: "GET", diff --git a/packages/api/test/unit/beacon/oapiSpec.test.ts b/packages/api/test/unit/beacon/oapiSpec.test.ts index 6b238bf8cd36..e5d473ab6a55 100644 --- a/packages/api/test/unit/beacon/oapiSpec.test.ts +++ b/packages/api/test/unit/beacon/oapiSpec.test.ts @@ -56,7 +56,6 @@ const testDatas = { const ignoredOperations = [ /* missing route */ "getDepositSnapshot", // Won't fix for now, see https://github.com/ChainSafe/lodestar/issues/5697 - "getBlindedBlock", // https://github.com/ChainSafe/lodestar/issues/5699 "getNextWithdrawals", // https://github.com/ChainSafe/lodestar/issues/5696 "getDebugForkChoice", // https://github.com/ChainSafe/lodestar/issues/5700 /* Must support ssz response body */ diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index a6b48320bf8c..9a89abd68a14 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -38,6 +38,13 @@ export const testData: GenericServerTestCases = { meta: {executionOptimistic: true, finalized: false, version: ForkName.bellatrix}, }, }, + getBlindedBlock: { + args: {blockId: "head"}, + res: { + data: ssz.deneb.SignedBlindedBeaconBlock.defaultValue(), + meta: {executionOptimistic: true, finalized: false, version: ForkName.deneb}, + }, + }, getBlockAttestations: { args: {blockId: "head"}, res: {data: [ssz.phase0.Attestation.defaultValue()], meta: {executionOptimistic: true, finalized: false}}, 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 274d88e5d508..177f58aebb95 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -1,7 +1,12 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; -import {computeEpochAtSlot, computeTimeAtSlot, reconstructFullBlockOrContents} from "@lodestar/state-transition"; -import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; +import { + computeEpochAtSlot, + computeTimeAtSlot, + reconstructFullBlockOrContents, + signedBeaconBlockToBlinded, +} from "@lodestar/state-transition"; +import {ForkExecution, SLOTS_PER_HISTORICAL_ROOT, isForkExecution} from "@lodestar/params"; import {sleep, fromHex, toHex} from "@lodestar/utils"; import { deneb, @@ -385,6 +390,21 @@ export function getBeaconBlockApi({ }; }, + async getBlindedBlock({blockId}) { + const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); + const fork = config.getForkName(block.message.slot); + return { + data: isForkExecution(fork) + ? signedBeaconBlockToBlinded(config, block as SignedBeaconBlock) + : block, + meta: { + executionOptimistic, + finalized, + version: fork, + }, + }; + }, + async getBlockAttestations({blockId}) { const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); return { diff --git a/packages/beacon-node/test/e2e/api/impl/beacon/block/endpoint.test.ts b/packages/beacon-node/test/e2e/api/impl/beacon/block/endpoint.test.ts new file mode 100644 index 000000000000..46b0e10580aa --- /dev/null +++ b/packages/beacon-node/test/e2e/api/impl/beacon/block/endpoint.test.ts @@ -0,0 +1,101 @@ +import {describe, beforeAll, afterAll, it, expect, vi} from "vitest"; +import {createBeaconConfig} from "@lodestar/config"; +import {ApiClient, WireFormat, getClient} from "@lodestar/api"; +import { + SignedBeaconBlock, + SignedBlindedBeaconBlock, + isBlindedExecutionPayload, + isBlindedSignedBeaconBlock, + isExecutionPayload, +} from "@lodestar/types"; +import {ForkName} from "@lodestar/params"; +import {LogLevel, testLogger} from "../../../../../utils/logger.js"; +import {getDevBeaconNode} from "../../../../../utils/node/beacon.js"; +import {BeaconNode} from "../../../../../../src/node/nodejs.js"; +import {getConfig} from "../../../../../utils/config.js"; + +describe("beacon block api", function () { + vi.setConfig({testTimeout: 60_000, hookTimeout: 60_000}); + + const restPort = 9596; + const fork = ForkName.deneb; + const config = createBeaconConfig(getConfig(fork), Buffer.alloc(32, 0xaa)); + const validatorCount = 8; + + let bn: BeaconNode; + let client: ApiClient["beacon"]; + + beforeAll(async () => { + bn = await getDevBeaconNode({ + params: config, + options: { + sync: {isSingleNode: true}, + network: {allowPublishToZeroPeers: true}, + api: { + rest: { + enabled: true, + port: restPort, + }, + }, + chain: {blsVerifyAllMainThread: true}, + }, + validatorCount, + logger: testLogger("Node-A", {level: LogLevel.info}), + }); + client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).beacon; + }); + + afterAll(async () => { + await bn.close(); + }); + + describe("getBlockV2", () => { + it("should return signed beacon block", async () => { + const res = await client.getBlockV2({blockId: "head"}); + + expect(res.meta().version).toBe(fork); + expect(res.wireFormat()).toBe(WireFormat.ssz); + + const beaconBlock = res.value() as SignedBeaconBlock; + + expect(isBlindedSignedBeaconBlock(beaconBlock)).toBe(false); + expect(isExecutionPayload(beaconBlock.message.body.executionPayload)).toBe(true); + expect(beaconBlock.message.body).not.toHaveProperty("executionPayloadHeader"); + }); + + it("should return 400 if block id is invalid", async () => { + const res = await client.getBlockV2({blockId: "current"}); + expect(res.status).toBe(400); + }); + + it("should return 404 if block not found", async () => { + const res = await client.getBlockV2({blockId: 999}); + expect(res.status).toBe(404); + }); + }); + + describe("getBlindedBlock", () => { + it("should return signed blinded block", async () => { + const res = await client.getBlindedBlock({blockId: "head"}); + + expect(res.meta().version).toBe(fork); + expect(res.wireFormat()).toBe(WireFormat.ssz); + + const blindedBlock = res.value() as SignedBlindedBeaconBlock; + + expect(isBlindedSignedBeaconBlock(blindedBlock)).toBe(true); + expect(isBlindedExecutionPayload(blindedBlock.message.body.executionPayloadHeader)).toBe(true); + expect(blindedBlock.message.body).not.toHaveProperty("executionPayload"); + }); + + it("should return 400 if block id is invalid", async () => { + const res = await client.getBlindedBlock({blockId: "current"}); + expect(res.status).toBe(400); + }); + + it("should return 404 if block not found", async () => { + const res = await client.getBlindedBlock({blockId: 999}); + expect(res.status).toBe(404); + }); + }); +}); diff --git a/packages/state-transition/src/util/blindedBlock.ts b/packages/state-transition/src/util/blindedBlock.ts index d63b40a9fe88..2e4e4d590817 100644 --- a/packages/state-transition/src/util/blindedBlock.ts +++ b/packages/state-transition/src/util/blindedBlock.ts @@ -52,10 +52,20 @@ export function blindedOrFullBlockToHeader( export function beaconBlockToBlinded(config: ChainForkConfig, block: BeaconBlock): BlindedBeaconBlock { const fork = config.getForkName(block.slot); const executionPayloadHeader = executionPayloadToPayloadHeader(ForkSeq[fork], block.body.executionPayload); - const blindedBlock = {...block, body: {...block.body, executionPayloadHeader}} as BlindedBeaconBlock; + const blindedBlock: BlindedBeaconBlock = {...block, body: {...block.body, executionPayloadHeader}}; return blindedBlock; } +export function signedBeaconBlockToBlinded( + config: ChainForkConfig, + signedBlock: SignedBeaconBlock +): SignedBlindedBeaconBlock { + return { + message: beaconBlockToBlinded(config, signedBlock.message), + signature: signedBlock.signature, + }; +} + export function signedBlindedBlockToFull( signedBlindedBlock: SignedBlindedBeaconBlock, executionPayload: ExecutionPayload | null From 0f3109f322a352308ecdd47ac0d125ac33360d9d Mon Sep 17 00:00:00 2001 From: g11tech Date: Tue, 25 Jun 2024 17:03:16 +0530 Subject: [PATCH 35/55] fix: update blockcontents typing to be more precise (#6907) --- packages/types/src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 4f844884b88e..46641d55667e 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -124,9 +124,9 @@ type TypesByFork = { BuilderBid: deneb.BuilderBid; SignedBuilderBid: deneb.SignedBuilderBid; SSEPayloadAttributes: deneb.SSEPayloadAttributes; - BlockContents: {block: BeaconBlock; kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs}; + BlockContents: {block: BeaconBlock; kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs}; SignedBlockContents: { - signedBlock: SignedBeaconBlock; + signedBlock: SignedBeaconBlock; kzgProofs: deneb.KZGProofs; blobs: deneb.Blobs; }; From fe23f68998cff78ee1e68c0701e9f38405268a36 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 25 Jun 2024 14:08:12 +0100 Subject: [PATCH 36/55] chore: rename execution payload header type guard (#6906) --- .../test/e2e/api/impl/beacon/block/endpoint.test.ts | 4 ++-- packages/types/src/utils/typeguards.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/beacon-node/test/e2e/api/impl/beacon/block/endpoint.test.ts b/packages/beacon-node/test/e2e/api/impl/beacon/block/endpoint.test.ts index 46b0e10580aa..ee0b17348099 100644 --- a/packages/beacon-node/test/e2e/api/impl/beacon/block/endpoint.test.ts +++ b/packages/beacon-node/test/e2e/api/impl/beacon/block/endpoint.test.ts @@ -4,9 +4,9 @@ import {ApiClient, WireFormat, getClient} from "@lodestar/api"; import { SignedBeaconBlock, SignedBlindedBeaconBlock, - isBlindedExecutionPayload, isBlindedSignedBeaconBlock, isExecutionPayload, + isExecutionPayloadHeader, } from "@lodestar/types"; import {ForkName} from "@lodestar/params"; import {LogLevel, testLogger} from "../../../../../utils/logger.js"; @@ -84,7 +84,7 @@ describe("beacon block api", function () { const blindedBlock = res.value() as SignedBlindedBeaconBlock; expect(isBlindedSignedBeaconBlock(blindedBlock)).toBe(true); - expect(isBlindedExecutionPayload(blindedBlock.message.body.executionPayloadHeader)).toBe(true); + expect(isExecutionPayloadHeader(blindedBlock.message.body.executionPayloadHeader)).toBe(true); expect(blindedBlock.message.body).not.toHaveProperty("executionPayload"); }); diff --git a/packages/types/src/utils/typeguards.ts b/packages/types/src/utils/typeguards.ts index 7848be700b00..f006227e03c9 100644 --- a/packages/types/src/utils/typeguards.ts +++ b/packages/types/src/utils/typeguards.ts @@ -23,7 +23,7 @@ export function isExecutionPayload( return (payload as ExecutionPayload).transactions !== undefined; } -export function isBlindedExecutionPayload( +export function isExecutionPayloadHeader( payload: ExecutionPayload | ExecutionPayloadHeader ): payload is ExecutionPayloadHeader { // we just check transactionsRoot for determining as it the base field From 8cb08ff56f66022c84d8ca1a91e82dba290e644a Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 25 Jun 2024 19:40:22 +0100 Subject: [PATCH 37/55] fix: ignore lockfiles when listing validator public keys (#6911) --- packages/cli/src/cmds/validator/list.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/src/cmds/validator/list.ts b/packages/cli/src/cmds/validator/list.ts index 4e867b042b37..007079852b77 100644 --- a/packages/cli/src/cmds/validator/list.ts +++ b/packages/cli/src/cmds/validator/list.ts @@ -22,6 +22,9 @@ export const list: CliCommand = { handler: async (args) => { const {network} = getBeaconConfigFromArgs(args); + // Ignore lockfiles to allow listing while validator client is running + args.force = true; + const signers = await getSignersFromArgs(args, network, {logger: console, signal: new AbortController().signal}); logSigners(console, signers); From d87a9018e6c071f3957de4356ac9bfd8ccd4e523 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 27 Jun 2024 22:03:40 +0100 Subject: [PATCH 38/55] docs: update api package readme example (#6915) --- packages/api/README.md | 23 ++++++++--------------- packages/api/package.json | 2 +- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/api/README.md b/packages/api/README.md index 3bcd397a9139..67cca2d06cd6 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -7,30 +7,23 @@ > This package is part of [ChainSafe's Lodestar](https://lodestar.chainsafe.io) project -Typescript REST client for the [Ethereum Consensus API spec](https://github.com/ethereum/beacon-apis) +Typescript REST client for the [Ethereum Consensus API](https://github.com/ethereum/beacon-apis) ## Usage -We use more typesafe approach for the API client, where all the errors are returned not thrown. This approach is more easy to document and better to handle all possible error cases. +The REST client extends the native [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), it behaves very similar in terms of error and response handling. It returns the same [Response object](https://developer.mozilla.org/en-US/docs/Web/API/Response) with additional methods to simplify usage and it allows to override all [Request options](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#options) if needed. ```typescript -import {getClient, HttpError} from "@lodestar/api"; +import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; const api = getClient({baseUrl: "http://localhost:9596"}, {config}); -api.beacon - .getStateValidator( - "head", - "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95" - ) - .then((res) => { - if (res.ok) { - console.log("Your balance is:", res.response.data.balance, res.ok, res.status); - } else { - console.error(res.status, res.error.code, res.error.message); - } - }); +const res = await api.beacon.getStateValidator({stateId: "head", validatorId: 0}); + +const validator = res.value(); + +console.log("The validator balance is: ", validator.balance); ``` ## Prerequisites diff --git a/packages/api/package.json b/packages/api/package.json index 67b966fcfb30..cd989ec0003f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/api", - "description": "A Typescript implementation of the Ethereum Consensus light client", + "description": "A Typescript REST client for the Ethereum Consensus API", "license": "Apache-2.0", "author": "ChainSafe Systems", "homepage": "https://github.com/ChainSafe/lodestar#readme", From f69bc110e469325d8b987b2e687e4fe6750f1cd2 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Thu, 27 Jun 2024 23:19:07 +0200 Subject: [PATCH 39/55] chore: remove all* types objects (#6904) * Remove all types * Add unit tests * Update the function name * Update fork utility functions * Cleanup utility usage * Fix lint errors * Fix types * Update spec tests * Update the types usage * Fix the lint errors * Update code as per feedback * Update code as per feedback * Fix lint errors * Fix lint errors * chore: fix type inference of exclude function (#6916) --------- Co-authored-by: Nico Flaig --- packages/api/src/builder/routes.ts | 4 +- .../src/chain/lightClient/index.ts | 5 +- .../chain/produceBlock/produceBlockBody.ts | 5 +- .../beacon-node/src/network/gossip/topic.ts | 6 +- .../beacon-node/src/network/reqresp/types.ts | 13 +-- .../src/node/utils/interop/state.ts | 5 +- .../test/spec/presets/genesis.test.ts | 8 +- .../test/spec/presets/light_client/sync.ts | 6 +- .../presets/light_client/update_ranking.ts | 4 +- .../test/spec/presets/operations.test.ts | 6 +- .../test/spec/presets/ssz_static.test.ts | 9 +- packages/config/src/forkConfig/index.ts | 8 +- packages/params/src/forkName.ts | 33 ++++++ .../unit/__snapshots__/forkName.test.ts.snap | 41 +++++++ packages/params/test/unit/forkName.test.ts | 61 +++++++++++ packages/types/src/sszTypes.ts | 102 ++---------------- 16 files changed, 176 insertions(+), 140 deletions(-) create mode 100644 packages/params/test/unit/__snapshots__/forkName.test.ts.snap create mode 100644 packages/params/test/unit/forkName.test.ts diff --git a/packages/api/src/builder/routes.ts b/packages/api/src/builder/routes.ts index 971f4a1e63fe..297c4fc5e9a9 100644 --- a/packages/api/src/builder/routes.ts +++ b/packages/api/src/builder/routes.ts @@ -27,7 +27,7 @@ import { JsonOnlyReq, WithVersion, } from "../utils/codecs.js"; -import {getExecutionForkTypes, toForkName} from "../utils/fork.js"; +import {getBlobsForkTypes, getExecutionForkTypes, toForkName} from "../utils/fork.js"; import {fromHeaders} from "../utils/headers.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -150,7 +150,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions((fork: ForkName) => { return isForkBlobs(fork) - ? ssz.allForksBlobs[fork].ExecutionPayloadAndBlobsBundle + ? getBlobsForkTypes(fork).ExecutionPayloadAndBlobsBundle : getExecutionForkTypes(fork).ExecutionPayload; }), meta: VersionCodec, diff --git a/packages/beacon-node/src/chain/lightClient/index.ts b/packages/beacon-node/src/chain/lightClient/index.ts index 5d25985df66e..6d713109f945 100644 --- a/packages/beacon-node/src/chain/lightClient/index.ts +++ b/packages/beacon-node/src/chain/lightClient/index.ts @@ -13,6 +13,7 @@ import { RootHex, Slot, ssz, + sszTypesFor, SSZTypesFor, SyncPeriod, } from "@lodestar/types"; @@ -39,6 +40,8 @@ import { ForkSeq, ForkExecution, ForkLightClient, + highestFork, + forkLightClient, } from "@lodestar/params"; import {IBeaconDb} from "../../db/index.js"; @@ -220,7 +223,7 @@ export class LightClientServer { this.zero = { // Assign the hightest fork's default value because it can always be typecasted down to correct fork - finalizedHeader: Object.values(ssz.allForksLightClient).slice(-1)[0].LightClientHeader.defaultValue(), + finalizedHeader: sszTypesFor(highestFork(forkLightClient)).LightClientHeader.defaultValue(), finalityBranch: ssz.altair.LightClientUpdate.fields["finalityBranch"].defaultValue(), }; diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index 5165b6b3f7ff..214fbdc890ec 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -16,6 +16,7 @@ import { ExecutionPayloadHeader, BlindedBeaconBlockBody, BlindedBeaconBlock, + sszTypesFor, } from "@lodestar/types"; import { CachedBeaconStateAllForks, @@ -240,7 +241,7 @@ export async function produceBlockBody( if (prepareRes.isPremerge) { (blockBody as BeaconBlockBody).executionPayload = - ssz.allForksExecution[fork].ExecutionPayload.defaultValue(); + sszTypesFor(fork).ExecutionPayload.defaultValue(); blobsResult = {type: BlobsResultType.preDeneb}; executionPayloadValue = BigInt(0); } else { @@ -310,7 +311,7 @@ export async function produceBlockBody( e as Error ); (blockBody as BeaconBlockBody).executionPayload = - ssz.allForksExecution[fork].ExecutionPayload.defaultValue(); + sszTypesFor(fork).ExecutionPayload.defaultValue(); blobsResult = {type: BlobsResultType.preDeneb}; executionPayloadValue = BigInt(0); } else { diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index c5cd68ffa1de..0f3f6942a10a 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -1,4 +1,4 @@ -import {phase0, ssz} from "@lodestar/types"; +import {phase0, ssz, sszTypesFor} from "@lodestar/types"; import {ForkDigestContext} from "@lodestar/config"; import { ATTESTATION_SUBNET_COUNT, @@ -102,11 +102,11 @@ export function getGossipSSZType(topic: GossipTopic) { return ssz.altair.SyncCommitteeMessage; case GossipType.light_client_optimistic_update: return isForkLightClient(topic.fork) - ? ssz.allForksLightClient[topic.fork].LightClientOptimisticUpdate + ? sszTypesFor(topic.fork).LightClientOptimisticUpdate : ssz.altair.LightClientOptimisticUpdate; case GossipType.light_client_finality_update: return isForkLightClient(topic.fork) - ? ssz.allForksLightClient[topic.fork].LightClientFinalityUpdate + ? sszTypesFor(topic.fork).LightClientFinalityUpdate : ssz.altair.LightClientFinalityUpdate; case GossipType.bls_to_execution_change: return ssz.capella.SignedBLSToExecutionChange; diff --git a/packages/beacon-node/src/network/reqresp/types.ts b/packages/beacon-node/src/network/reqresp/types.ts index 78625f6ea414..36fa0a4f2632 100644 --- a/packages/beacon-node/src/network/reqresp/types.ts +++ b/packages/beacon-node/src/network/reqresp/types.ts @@ -1,7 +1,7 @@ import {Type} from "@chainsafe/ssz"; import {ForkLightClient, ForkName, isForkLightClient} from "@lodestar/params"; import {Protocol, ProtocolHandler, ReqRespRequest} from "@lodestar/reqresp"; -import {Metadata, Root, SignedBeaconBlock, altair, deneb, phase0, ssz} from "@lodestar/types"; +import {Metadata, Root, SignedBeaconBlock, altair, deneb, phase0, ssz, sszTypesFor} from "@lodestar/types"; export type ProtocolNoHandler = Omit; @@ -91,14 +91,11 @@ export const responseSszTypeByMethod: {[K in ReqRespMethod]: ResponseTypeGetter< [ReqRespMethod.BeaconBlocksByRoot]: blocksResponseType, [ReqRespMethod.BlobSidecarsByRange]: () => ssz.deneb.BlobSidecar, [ReqRespMethod.BlobSidecarsByRoot]: () => ssz.deneb.BlobSidecar, - [ReqRespMethod.LightClientBootstrap]: (fork) => - ssz.allForksLightClient[onlyLightclientFork(fork)].LightClientBootstrap, - [ReqRespMethod.LightClientUpdatesByRange]: (fork) => - ssz.allForksLightClient[onlyLightclientFork(fork)].LightClientUpdate, - [ReqRespMethod.LightClientFinalityUpdate]: (fork) => - ssz.allForksLightClient[onlyLightclientFork(fork)].LightClientFinalityUpdate, + [ReqRespMethod.LightClientBootstrap]: (fork) => sszTypesFor(onlyLightclientFork(fork)).LightClientBootstrap, + [ReqRespMethod.LightClientUpdatesByRange]: (fork) => sszTypesFor(onlyLightclientFork(fork)).LightClientUpdate, + [ReqRespMethod.LightClientFinalityUpdate]: (fork) => sszTypesFor(onlyLightclientFork(fork)).LightClientFinalityUpdate, [ReqRespMethod.LightClientOptimisticUpdate]: (fork) => - ssz.allForksLightClient[onlyLightclientFork(fork)].LightClientOptimisticUpdate, + sszTypesFor(onlyLightclientFork(fork)).LightClientOptimisticUpdate, }; function onlyLightclientFork(fork: ForkName): ForkLightClient { diff --git a/packages/beacon-node/src/node/utils/interop/state.ts b/packages/beacon-node/src/node/utils/interop/state.ts index f3efc9894587..6528bd392bc7 100644 --- a/packages/beacon-node/src/node/utils/interop/state.ts +++ b/packages/beacon-node/src/node/utils/interop/state.ts @@ -1,4 +1,4 @@ -import {Bytes32, phase0, ssz, TimeSeconds} from "@lodestar/types"; +import {Bytes32, phase0, ssz, sszTypesFor, TimeSeconds} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {BeaconStateAllForks, initializeBeaconStateFromEth1} from "@lodestar/state-transition"; import {createEmptyEpochCacheImmutableData} from "@lodestar/state-transition"; @@ -12,7 +12,6 @@ export const INTEROP_TIMESTAMP = Math.pow(2, 40); // Note: These configuration settings do not apply to the mainnet and are utilized only by pure Merge testing. export const GENESIS_GAS_LIMIT = 30000000; export const GENESIS_BASE_FEE_PER_GAS = BigInt(1000000000); -type ExecutionFork = Exclude; export type InteropStateOpts = { genesisTime?: number; @@ -34,7 +33,7 @@ export function getInteropState( const fork = config.getForkName(GENESIS_SLOT); const executionPayloadHeaderType = fork !== ForkName.phase0 && fork !== ForkName.altair - ? ssz.allForksExecution[fork as ExecutionFork].ExecutionPayloadHeader + ? sszTypesFor(fork).ExecutionPayloadHeader : ssz.bellatrix.ExecutionPayloadHeader; const latestPayloadHeader = executionPayloadHeaderType.defaultViewDU(); // TODO: when having different test options, consider modifying these values diff --git a/packages/beacon-node/test/spec/presets/genesis.test.ts b/packages/beacon-node/test/spec/presets/genesis.test.ts index 00ba11bc1e8e..f03f2595a566 100644 --- a/packages/beacon-node/test/spec/presets/genesis.test.ts +++ b/packages/beacon-node/test/spec/presets/genesis.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; import {expect} from "vitest"; -import {phase0, Root, ssz, TimeSeconds, ExecutionPayloadHeader} from "@lodestar/types"; +import {phase0, Root, ssz, TimeSeconds, ExecutionPayloadHeader, sszTypesFor} from "@lodestar/types"; import {InputType} from "@lodestar/spec-test-util"; import { BeaconStateAllForks, @@ -49,7 +49,7 @@ const genesisInitialization: TestRunnerFn; - specTestIterator(path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), { genesis: {type: RunnerType.default, fn: genesis}, }); 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 84ab2c28b467..4b264e5b65e4 100644 --- a/packages/beacon-node/test/spec/presets/light_client/sync.ts +++ b/packages/beacon-node/test/spec/presets/light_client/sync.ts @@ -1,7 +1,7 @@ import {expect} from "vitest"; import {init} from "@chainsafe/bls/switchable"; import {isForkLightClient} from "@lodestar/params"; -import {altair, phase0, RootHex, Slot, ssz} from "@lodestar/types"; +import {altair, phase0, RootHex, Slot, ssz, sszTypesFor} from "@lodestar/types"; import {InputType} from "@lodestar/spec-test-util"; import {createBeaconConfig, ChainConfig} from "@lodestar/config"; import {fromHex, toHex} from "@lodestar/utils"; @@ -170,9 +170,7 @@ export const sync: TestRunnerFn = (fork) => { config: InputType.YAML, }, sszTypes: { - bootstrap: isForkLightClient(fork) - ? ssz.allForksLightClient[fork].LightClientBootstrap - : ssz.altair.LightClientBootstrap, + bootstrap: isForkLightClient(fork) ? sszTypesFor(fork).LightClientBootstrap : ssz.altair.LightClientBootstrap, // The updates are multifork and need config and step info to be deserialized within the test [UPDATE_FILE_NAME]: {typeName: "LightClientUpdate", deserialize: (bytes: Uint8Array) => bytes}, }, diff --git a/packages/beacon-node/test/spec/presets/light_client/update_ranking.ts b/packages/beacon-node/test/spec/presets/light_client/update_ranking.ts index 3ed71d6ff164..a2e60c1cc84a 100644 --- a/packages/beacon-node/test/spec/presets/light_client/update_ranking.ts +++ b/packages/beacon-node/test/spec/presets/light_client/update_ranking.ts @@ -1,5 +1,5 @@ import {expect} from "vitest"; -import {LightClientUpdate, altair, ssz} from "@lodestar/types"; +import {LightClientUpdate, altair, ssz, sszTypesFor} from "@lodestar/types"; import {isForkLightClient} from "@lodestar/params"; import {InputType} from "@lodestar/spec-test-util"; import {isBetterUpdate, LightClientUpdateSummary, toLightClientUpdateSummary} from "@lodestar/light-client/spec"; @@ -54,7 +54,7 @@ newUpdate = ${renderUpdate(newUpdate)} }, sszTypes: { [UPDATES_FILE_NAME]: isForkLightClient(fork) - ? ssz.allForksLightClient[fork].LightClientUpdate + ? sszTypesFor(fork).LightClientUpdate : ssz.altair.LightClientUpdate, }, expectFunc: () => {}, diff --git a/packages/beacon-node/test/spec/presets/operations.test.ts b/packages/beacon-node/test/spec/presets/operations.test.ts index e7cb8a90dbb4..4c1c10e0cb66 100644 --- a/packages/beacon-node/test/spec/presets/operations.test.ts +++ b/packages/beacon-node/test/spec/presets/operations.test.ts @@ -8,7 +8,7 @@ import { getBlockRootAtSlot, } from "@lodestar/state-transition"; import * as blockFns from "@lodestar/state-transition/block"; -import {ssz, phase0, altair, bellatrix, capella} from "@lodestar/types"; +import {ssz, phase0, altair, bellatrix, capella, sszTypesFor} from "@lodestar/types"; import {InputType} from "@lodestar/spec-test-util"; import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; @@ -128,7 +128,7 @@ const operations: TestRunnerFn = (fork, // Bellatrix execution_payload: fork !== ForkName.phase0 && fork !== ForkName.altair - ? ssz.allForksExecution[fork as ExecutionFork].ExecutionPayload + ? sszTypesFor(fork).ExecutionPayload : ssz.bellatrix.ExecutionPayload, // Capella address_change: ssz.capella.SignedBLSToExecutionChange, @@ -143,8 +143,6 @@ const operations: TestRunnerFn = (fork, }; }; -type ExecutionFork = Exclude; - specTestIterator(path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), { operations: {type: RunnerType.default, fn: operations}, }); diff --git a/packages/beacon-node/test/spec/presets/ssz_static.test.ts b/packages/beacon-node/test/spec/presets/ssz_static.test.ts index bcab25acde4c..d81b9dee0098 100644 --- a/packages/beacon-node/test/spec/presets/ssz_static.test.ts +++ b/packages/beacon-node/test/spec/presets/ssz_static.test.ts @@ -2,8 +2,8 @@ import fs from "node:fs"; import path from "node:path"; import {it, vi} from "vitest"; import {Type} from "@chainsafe/ssz"; -import {ssz} from "@lodestar/types"; -import {ACTIVE_PRESET, ForkName, ForkLightClient} from "@lodestar/params"; +import {ssz, sszTypesFor} from "@lodestar/types"; +import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; import {replaceUintTypeWithUintBigintType} from "../utils/replaceUintTypeWithUintBigintType.js"; import {parseSszStaticTestcase} from "../utils/sszTestCaseParser.js"; import {runValidSszTest} from "../utils/runValidSszTest.js"; @@ -44,10 +44,7 @@ const sszStatic = /* eslint-disable @typescript-eslint/strict-boolean-expressions */ const sszType = - // Since lightclient types are not updated/declared at all forks, this allForksLightClient - // will help us get the right type for lightclient objects - ((ssz.allForksLightClient[fork as ForkLightClient] || {}) as Types)[typeName] || - (ssz[fork] as Types)[typeName] || + (sszTypesFor(fork) as Types)[typeName] || (ssz.capella as Types)[typeName] || (ssz.bellatrix as Types)[typeName] || (ssz.altair as Types)[typeName] || diff --git a/packages/config/src/forkConfig/index.ts b/packages/config/src/forkConfig/index.ts index 358c2d752001..16d8952548b8 100644 --- a/packages/config/src/forkConfig/index.ts +++ b/packages/config/src/forkConfig/index.ts @@ -11,7 +11,7 @@ import { ForkLightClient, ForkBlobs, } from "@lodestar/params"; -import {Slot, Version, ssz, SSZTypesFor, sszTypesFor} from "@lodestar/types"; +import {Slot, Version, SSZTypesFor, sszTypesFor} from "@lodestar/types"; import {ChainConfig} from "../chainConfig/index.js"; import {ForkConfig, ForkInfo} from "./types.js"; @@ -99,21 +99,21 @@ export function createForkConfig(config: ChainConfig): ForkConfig { if (!isForkExecution(forkName)) { throw Error(`Invalid slot=${slot} fork=${forkName} for execution fork types`); } - return ssz.allForksExecution[forkName]; + return sszTypesFor(forkName); }, getLightClientForkTypes(slot: Slot): SSZTypesFor { const forkName = this.getForkName(slot); if (!isForkLightClient(forkName)) { throw Error(`Invalid slot=${slot} fork=${forkName} for lightclient fork types`); } - return ssz.allForksLightClient[forkName]; + return sszTypesFor(forkName); }, getBlobsForkTypes(slot: Slot): SSZTypesFor { const forkName = this.getForkName(slot); if (!isForkBlobs(forkName)) { throw Error(`Invalid slot=${slot} fork=${forkName} for blobs fork types`); } - return ssz.allForksBlobs[forkName]; + return sszTypesFor(forkName); }, }; } diff --git a/packages/params/src/forkName.ts b/packages/params/src/forkName.ts index fa3be24bfae4..a5f6d49d1cef 100644 --- a/packages/params/src/forkName.ts +++ b/packages/params/src/forkName.ts @@ -20,28 +20,61 @@ export enum ForkSeq { deneb = 4, } +function exclude(coll: T[], val: U[]): Exclude[] { + return coll.filter((f) => !val.includes(f as U)) as Exclude[]; +} + +export function highestFork(forkNames: F[]): F { + let highest = forkNames[0]; + + for (const forkName of forkNames) { + if (ForkSeq[forkName] > ForkSeq[highest]) { + highest = forkName; + } + } + + return highest; +} + +export function lowestFork(forkNames: F[]): F { + let lowest = forkNames[0]; + + for (const forkName of forkNames) { + if (ForkSeq[forkName] < ForkSeq[lowest]) { + lowest = forkName; + } + } + + return lowest; +} + export type ForkAll = ForkName; +export const forkAll = Object.values(ForkName); export type ForkPreLightClient = ForkName.phase0; export type ForkLightClient = Exclude; +export const forkLightClient = exclude(forkAll, [ForkName.phase0]); export function isForkLightClient(fork: ForkName): fork is ForkLightClient { return fork !== ForkName.phase0; } export type ForkPreExecution = ForkPreLightClient | ForkName.altair; export type ForkExecution = Exclude; +export const forkExecution = exclude(forkAll, [ForkName.phase0, ForkName.altair]); export function isForkExecution(fork: ForkName): fork is ForkExecution { return isForkLightClient(fork) && fork !== ForkName.altair; } export type ForkPreWithdrawals = ForkPreExecution | ForkName.bellatrix; export type ForkWithdrawals = Exclude; +export const forkWithdrawals = exclude(forkAll, [ForkName.phase0, ForkName.altair, ForkName.bellatrix]); export function isForkWithdrawals(fork: ForkName): fork is ForkWithdrawals { return isForkExecution(fork) && fork !== ForkName.bellatrix; } export type ForkPreBlobs = ForkPreWithdrawals | ForkName.capella; export type ForkBlobs = Exclude; +export const forkBlobs = exclude(forkAll, [ForkName.phase0, ForkName.altair, ForkName.bellatrix, ForkName.capella]); export function isForkBlobs(fork: ForkName): fork is ForkBlobs { return isForkWithdrawals(fork) && fork !== ForkName.capella; } diff --git a/packages/params/test/unit/__snapshots__/forkName.test.ts.snap b/packages/params/test/unit/__snapshots__/forkName.test.ts.snap new file mode 100644 index 000000000000..3d7009a7c2ec --- /dev/null +++ b/packages/params/test/unit/__snapshots__/forkName.test.ts.snap @@ -0,0 +1,41 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`forkName > should have valid allForks 1`] = ` +[ + "phase0", + "altair", + "bellatrix", + "capella", + "deneb", +] +`; + +exports[`forkName > should have valid blobs forks 1`] = ` +[ + "deneb", +] +`; + +exports[`forkName > should have valid execution forks 1`] = ` +[ + "bellatrix", + "capella", + "deneb", +] +`; + +exports[`forkName > should have valid lightclient forks 1`] = ` +[ + "altair", + "bellatrix", + "capella", + "deneb", +] +`; + +exports[`forkName > should have valid withdrawal forks 1`] = ` +[ + "capella", + "deneb", +] +`; diff --git a/packages/params/test/unit/forkName.test.ts b/packages/params/test/unit/forkName.test.ts new file mode 100644 index 000000000000..4385c1e10d8a --- /dev/null +++ b/packages/params/test/unit/forkName.test.ts @@ -0,0 +1,61 @@ +import {describe, it, expect} from "vitest"; +import { + ForkName, + forkAll, + forkBlobs, + forkExecution, + forkLightClient, + forkWithdrawals, + highestFork, + lowestFork, +} from "../../src/forkName.js"; + +describe("forkName", () => { + it("should have valid allForks", () => { + expect(forkAll).toMatchSnapshot(); + }); + + it("should have valid execution forks", () => { + expect(forkExecution).toMatchSnapshot(); + }); + + it("should have valid lightclient forks", () => { + expect(forkLightClient).toMatchSnapshot(); + }); + + it("should have valid withdrawal forks", () => { + expect(forkWithdrawals).toMatchSnapshot(); + }); + + it("should have valid blobs forks", () => { + expect(forkBlobs).toMatchSnapshot(); + }); + + describe("highestFork", () => { + it("should return the only fork as highest", () => { + expect(highestFork([ForkName.altair])).toBe(ForkName.altair); + }); + + it("should return the the highest fork", () => { + expect(highestFork([ForkName.altair, ForkName.bellatrix])).toBe(ForkName.bellatrix); + }); + + it("should return the the highest fork if given in random order", () => { + expect(highestFork([ForkName.altair, ForkName.bellatrix, ForkName.deneb, ForkName.phase0])).toBe(ForkName.deneb); + }); + }); + + describe("lowestFork", () => { + it("should return the only fork as lowest", () => { + expect(lowestFork([ForkName.altair])).toBe(ForkName.altair); + }); + + it("should return the the lowest fork", () => { + expect(lowestFork([ForkName.altair, ForkName.bellatrix])).toBe(ForkName.altair); + }); + + it("should return the the lowest fork if given in random order", () => { + expect(lowestFork([ForkName.altair, ForkName.bellatrix, ForkName.deneb, ForkName.phase0])).toBe(ForkName.phase0); + }); + }); +}); diff --git a/packages/types/src/sszTypes.ts b/packages/types/src/sszTypes.ts index eae1bbcdeaba..60980fa0822a 100644 --- a/packages/types/src/sszTypes.ts +++ b/packages/types/src/sszTypes.ts @@ -1,5 +1,5 @@ import {CompositeType, CompositeView, CompositeViewDU, ContainerType, ValueOf} from "@chainsafe/ssz"; -import {ForkBlobs, ForkExecution, ForkLightClient, ForkName} from "@lodestar/params"; +import {ForkName} from "@lodestar/params"; import {ssz as phase0} from "./phase0/index.js"; import {ssz as altair} from "./altair/index.js"; import {ssz as bellatrix} from "./bellatrix/index.js"; @@ -14,103 +14,13 @@ export {phase0, altair, bellatrix, capella, deneb}; * A record of AllForksSSZTypes indexed by fork */ const typesByFork = { - [ForkName.phase0]: { - BeaconBlock: phase0.BeaconBlock, - BeaconBlockBody: phase0.BeaconBlockBody, - BeaconState: phase0.BeaconState, - SignedBeaconBlock: phase0.SignedBeaconBlock, - Metadata: phase0.Metadata, - }, - [ForkName.altair]: { - BeaconBlock: altair.BeaconBlock, - BeaconBlockBody: altair.BeaconBlockBody, - BeaconState: altair.BeaconState, - SignedBeaconBlock: altair.SignedBeaconBlock, - Metadata: altair.Metadata, - LightClientHeader: altair.LightClientHeader, - LightClientBootstrap: altair.LightClientBootstrap, - LightClientUpdate: altair.LightClientUpdate, - LightClientFinalityUpdate: altair.LightClientFinalityUpdate, - LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate, - LightClientStore: altair.LightClientStore, - }, - [ForkName.bellatrix]: { - BeaconBlock: bellatrix.BeaconBlock, - BeaconBlockBody: bellatrix.BeaconBlockBody, - BeaconState: bellatrix.BeaconState, - SignedBeaconBlock: bellatrix.SignedBeaconBlock, - Metadata: altair.Metadata, - LightClientHeader: altair.LightClientHeader, - LightClientBootstrap: altair.LightClientBootstrap, - LightClientUpdate: altair.LightClientUpdate, - LightClientFinalityUpdate: altair.LightClientFinalityUpdate, - LightClientOptimisticUpdate: altair.LightClientOptimisticUpdate, - LightClientStore: altair.LightClientStore, - BlindedBeaconBlock: bellatrix.BlindedBeaconBlock, - BlindedBeaconBlockBody: bellatrix.BlindedBeaconBlockBody, - SignedBlindedBeaconBlock: bellatrix.SignedBlindedBeaconBlock, - ExecutionPayload: bellatrix.ExecutionPayload, - ExecutionPayloadHeader: bellatrix.ExecutionPayloadHeader, - BuilderBid: bellatrix.BuilderBid, - SignedBuilderBid: bellatrix.SignedBuilderBid, - SSEPayloadAttributes: bellatrix.SSEPayloadAttributes, - }, - [ForkName.capella]: { - BeaconBlock: capella.BeaconBlock, - BeaconBlockBody: capella.BeaconBlockBody, - BeaconState: capella.BeaconState, - SignedBeaconBlock: capella.SignedBeaconBlock, - Metadata: altair.Metadata, - LightClientHeader: capella.LightClientHeader, - LightClientBootstrap: capella.LightClientBootstrap, - LightClientUpdate: capella.LightClientUpdate, - LightClientFinalityUpdate: capella.LightClientFinalityUpdate, - LightClientOptimisticUpdate: capella.LightClientOptimisticUpdate, - LightClientStore: capella.LightClientStore, - BlindedBeaconBlock: capella.BlindedBeaconBlock, - BlindedBeaconBlockBody: capella.BlindedBeaconBlockBody, - SignedBlindedBeaconBlock: capella.SignedBlindedBeaconBlock, - ExecutionPayload: capella.ExecutionPayload, - ExecutionPayloadHeader: capella.ExecutionPayloadHeader, - BuilderBid: capella.BuilderBid, - SignedBuilderBid: capella.SignedBuilderBid, - SSEPayloadAttributes: capella.SSEPayloadAttributes, - }, - [ForkName.deneb]: { - BeaconBlock: deneb.BeaconBlock, - BeaconBlockBody: deneb.BeaconBlockBody, - BeaconState: deneb.BeaconState, - SignedBeaconBlock: deneb.SignedBeaconBlock, - Metadata: altair.Metadata, - LightClientHeader: deneb.LightClientHeader, - LightClientBootstrap: deneb.LightClientBootstrap, - LightClientUpdate: deneb.LightClientUpdate, - LightClientFinalityUpdate: deneb.LightClientFinalityUpdate, - LightClientOptimisticUpdate: deneb.LightClientOptimisticUpdate, - LightClientStore: deneb.LightClientStore, - BlindedBeaconBlock: deneb.BlindedBeaconBlock, - BlindedBeaconBlockBody: deneb.BlindedBeaconBlockBody, - SignedBlindedBeaconBlock: deneb.SignedBlindedBeaconBlock, - ExecutionPayload: deneb.ExecutionPayload, - ExecutionPayloadHeader: deneb.ExecutionPayloadHeader, - BuilderBid: deneb.BuilderBid, - SignedBuilderBid: deneb.SignedBuilderBid, - SSEPayloadAttributes: deneb.SSEPayloadAttributes, - ExecutionPayloadAndBlobsBundle: deneb.ExecutionPayloadAndBlobsBundle, - }, + [ForkName.phase0]: {...phase0}, + [ForkName.altair]: {...phase0, ...altair}, + [ForkName.bellatrix]: {...phase0, ...altair, ...bellatrix}, + [ForkName.capella]: {...phase0, ...altair, ...bellatrix, ...capella}, + [ForkName.deneb]: {...phase0, ...altair, ...bellatrix, ...capella, ...deneb}, }; -const pick = , K extends keyof T>(obj: T, ...keys: K[]): Pick => - Object.fromEntries(keys.filter((key) => key in obj).map((key) => [key, obj[key]])) as Pick; - -const executionForks: ForkExecution[] = [ForkName.bellatrix, ForkName.capella, ForkName.deneb]; -const lightCLientForks: ForkLightClient[] = [ForkName.altair, ForkName.bellatrix, ForkName.capella, ForkName.deneb]; -const blobsForks: ForkBlobs[] = [ForkName.deneb]; - -export const allForksExecution = pick(typesByFork, ...executionForks); -export const allForksLightClient = pick(typesByFork, ...lightCLientForks); -export const allForksBlobs = pick(typesByFork, ...blobsForks); - /** * A type of union of forks must accept as any parameter the UNION of all fork types. */ From 48e6bac36b39263a6fdc47b1cba59d47d231bda8 Mon Sep 17 00:00:00 2001 From: Cayman Date: Sat, 29 Jun 2024 10:01:18 -0400 Subject: [PATCH 40/55] feat: add to block processor and bls dashboards (#6917) * feat: add to block processor and bls dashboards * chore: fix up dashboards --- dashboards/lodestar_block_processor.json | 2493 +++++++++++++++++++++- dashboards/lodestar_bls_thread_pool.json | 191 +- 2 files changed, 2670 insertions(+), 14 deletions(-) diff --git a/dashboards/lodestar_block_processor.json b/dashboards/lodestar_block_processor.json index b7871f202446..dca74c0180ee 100644 --- a/dashboards/lodestar_block_processor.json +++ b/dashboards/lodestar_block_processor.json @@ -97,6 +97,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -179,6 +180,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -264,6 +266,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -348,6 +351,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -432,6 +436,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -516,6 +521,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -600,6 +606,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -757,7 +764,8 @@ "layout": "auto" }, "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -765,7 +773,7 @@ "reverse": false } }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -861,7 +869,8 @@ "layout": "auto" }, "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -869,7 +878,7 @@ "reverse": false } }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -941,7 +950,8 @@ "layout": "auto" }, "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -949,7 +959,7 @@ "reverse": false } }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -980,6 +990,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1066,6 +1077,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1151,6 +1163,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1233,6 +1246,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1318,6 +1332,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1404,6 +1419,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1504,6 +1520,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1604,6 +1621,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1704,6 +1722,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1805,6 +1824,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1927,6 +1947,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2008,6 +2029,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2135,7 +2157,8 @@ "layout": "auto" }, "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -2143,7 +2166,7 @@ "reverse": false } }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -2209,7 +2232,7 @@ "content": "Verifies signature sets in a thread pool of workers. Must ensure that signatures are verified fast and efficiently.", "mode": "markdown" }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -2237,6 +2260,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2318,6 +2342,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2399,6 +2424,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2486,6 +2512,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2573,6 +2600,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2660,6 +2688,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2747,6 +2776,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2845,6 +2875,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2932,6 +2963,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3016,6 +3048,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3127,6 +3160,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3236,6 +3270,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3348,6 +3383,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3456,6 +3492,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3548,6 +3585,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3628,6 +3666,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3737,6 +3776,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3822,6 +3862,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -3923,6 +3964,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -4048,6 +4090,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -4166,6 +4209,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -4307,6 +4351,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -4387,11 +4432,2437 @@ ], "title": "Proto Array", "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 190 + }, + "id": 538, + "panels": [], + "title": "Gossip Block", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block slot time and the time block received via gossip", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 191 + }, + "id": 539, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_elapsed_time_till_received_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block recv delay", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block slot time and the time block processed", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 191 + }, + "id": 540, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_elapsed_time_till_processed_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block process delay", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block slot time and the time block received via gossip", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 199 + }, + "id": 553, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_elapsed_time_till_received_sum[$rate_interval]) / rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block recv delay", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block slot time and the time block processed", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 12, + "y": 199 + }, + "id": 554, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_elapsed_time_till_processed_sum[$rate_interval]) / rate(lodestar_gossip_block_elapsed_time_till_processed_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block process delay", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and block validated", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 207 + }, + "id": 556, + "options": { + "calculate": false, + "calculation": { + "yBuckets": { + "scale": { + "type": "linear" + } + } + }, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_gossip_validate_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block recv to gossip validation delay", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time to apply gossip validations", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 207 + }, + "id": 542, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Gossip validation time", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and block validated", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 215 + }, + "id": 541, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_gossip_validate_sum[$rate_interval]) / rate(lodestar_gossip_block_received_to_gossip_validate_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block recv to gossip validation delay", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time to apply gossip validations", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 12, + "y": 215 + }, + "id": 555, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "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])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg gossip validation time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and block state transition", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 223 + }, + "id": 557, + "options": { + "calculate": false, + "calculation": { + "yBuckets": { + "scale": { + "type": "linear" + } + } + }, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_state_transition_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block recv to state transition delay", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time to validate block state transition", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 223 + }, + "id": 544, + "options": { + "calculate": false, + "calculation": { + "yBuckets": { + "scale": { + "type": "linear" + } + } + }, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_state_transition_time_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block state transition", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and block state transition", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 231 + }, + "id": 543, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_state_transition_sum[$rate_interval]) / rate(lodestar_gossip_block_received_to_state_transition_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block recv to state transition delay", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time to validate block state transition", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 12, + "y": 231 + }, + "id": 558, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_state_transition_time_sum[$rate_interval]) / rate(lodestar_gossip_block_state_transition_time_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block state transition", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and block signatures verification", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 239 + }, + "id": 559, + "options": { + "calculate": false, + "calculation": { + "yBuckets": { + "scale": { + "type": "linear" + } + } + }, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_signatures_verification_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block recv to sig verification delay", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed for block signatures verification", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 239 + }, + "id": 546, + "options": { + "calculate": false, + "calculation": { + "yBuckets": { + "scale": { + "type": "linear" + } + } + }, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_signatures_verification_time_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block sig verification", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and block signatures verification", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 247 + }, + "id": 545, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_signatures_verification_sum[$rate_interval]) / rate(lodestar_gossip_block_received_to_signatures_verification_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block recv to sig verification delay", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed for block signatures verification", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 12, + "y": 247 + }, + "id": 560, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_signatures_verification_time_sum[$rate_interval]) / rate(lodestar_gossip_block_signatures_verification_time_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block sig verification", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and execution payload verification", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 255 + }, + "id": 561, + "options": { + "calculate": false, + "calculation": { + "yBuckets": { + "scale": { + "type": "linear" + } + } + }, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_execution_payload_verification_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block recv to execution payload verification delay", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed for execution payload verification", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 255 + }, + "id": 548, + "options": { + "calculate": false, + "calculation": { + "yBuckets": { + "scale": { + "type": "linear" + } + } + }, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_execution_payload_verification_time_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block execution payload verification", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and execution payload verification", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 263 + }, + "id": 547, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_execution_payload_verification_sum[$rate_interval]) / rate(lodestar_gossip_block_received_to_execution_payload_verification_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block recv to execution payload verification delay", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed for execution payload verification", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 12, + "y": 263 + }, + "id": 562, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_execution_payload_verification_time_sum[$rate_interval]) / rate(lodestar_gossip_execution_payload_verification_time_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block execution payload verification", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and block import", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 271 + }, + "id": 563, + "options": { + "calculate": false, + "calculation": { + "yBuckets": { + "scale": { + "type": "linear" + } + } + }, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_block_import_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block recv to import delay", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed for block import", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 271 + }, + "id": 550, + "options": { + "calculate": false, + "calculation": { + "yBuckets": { + "scale": { + "type": "linear" + } + } + }, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_block_import_time_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Block import", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and block import", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 279 + }, + "id": 549, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_block_import_sum[$rate_interval]) / rate(lodestar_gossip_block_received_to_block_import_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block recv to import delay", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed for block import", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 12, + "y": 279 + }, + "id": 564, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_block_import_time_sum[$rate_interval]) / rate(lodestar_gossip_block_block_import_time_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "time", + "range": true, + "refId": "A" + } + ], + "title": "Avg block import", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and blobs became available", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 287 + }, + "id": 565, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_blobs_availability_time_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Block recv to blob availability delay", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block verified and blobs became available", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 287 + }, + "id": 552, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "s" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_verified_to_blobs_availability_time_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Block verified to blob availability delay", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block received and blobs became available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 295 + }, + "id": 551, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_received_to_blobs_availability_time_sum[$rate_interval]) / rate(lodestar_gossip_block_received_to_blobs_availability_time_count[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{numBlobs}} blobs", + "range": true, + "refId": "A" + } + ], + "title": "Avg block recv to blob availability delay", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Time elapsed between block verified and blobs became available", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "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": 12, + "y": 295 + }, + "id": 566, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_gossip_block_verified_to_blobs_availability_time_sum[$rate_interval]) / rate(lodestar_gossip_block_verified_to_blobs_availability_time_count[$rate_interval])", + "format": "time_series", + "instant": false, + "legendFormat": "{{numBlobs}} blobs", + "range": true, + "refId": "A" + } + ], + "title": "Avg block verified to blob availability delay", + "type": "timeseries" } ], "refresh": "10s", - "schemaVersion": 38, - "style": "dark", + "schemaVersion": 39, "tags": [ "lodestar" ], diff --git a/dashboards/lodestar_bls_thread_pool.json b/dashboards/lodestar_bls_thread_pool.json index 160312a92d57..92b7d00d3783 100644 --- a/dashboards/lodestar_bls_thread_pool.json +++ b/dashboards/lodestar_bls_thread_pool.json @@ -100,7 +100,7 @@ "content": "Verifies signature sets in a thread pool of workers. Must ensure that signatures are verified fast and efficiently.", "mode": "markdown" }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -128,6 +128,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -209,6 +210,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -290,6 +292,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -377,6 +380,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -464,6 +468,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -551,6 +556,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -638,6 +644,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -736,6 +743,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -823,6 +831,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -907,6 +916,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1018,6 +1028,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1101,6 +1112,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1184,11 +1196,184 @@ ], "title": "BLS jobItemWorkReq cpu time per epoch", "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": 42 + }, + "id": 521, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Magma", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 3, + "reverse": false, + "unit": "short" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_bucket[$rate_interval])", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Main Thread Signature Aggregation 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": 50 + }, + "id": 522, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Magma", + "steps": 128 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "decimals": 3, + "reverse": false, + "unit": "short" + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "rate(lodestar_bls_thread_pool_pubkeys_aggregation_main_thread_time_seconds_bucket[$rate_interval])", + "format": "heatmap", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Main Thread Public Key Aggregation Time", + "type": "heatmap" } ], "refresh": "10s", - "schemaVersion": 38, - "style": "dark", + "schemaVersion": 39, "tags": [ "lodestar" ], From e6c559f19994d8bbea1711bb73ccbe689195ad38 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Sat, 29 Jun 2024 18:17:02 +0100 Subject: [PATCH 41/55] fix: do not report negative sync time and slots/s (#6918) --- packages/beacon-node/src/node/notifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/node/notifier.ts b/packages/beacon-node/src/node/notifier.ts index 8c393a4fcb05..19bc36d48dc2 100644 --- a/packages/beacon-node/src/node/notifier.ts +++ b/packages/beacon-node/src/node/notifier.ts @@ -92,7 +92,7 @@ export async function runNodeNotifier(modules: NodeNotifierModules): Promise Date: Mon, 1 Jul 2024 18:33:24 +0100 Subject: [PATCH 42/55] chore: unhide execution engine mock flag (#6922) --- packages/cli/src/options/beaconNodeOptions/execution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/options/beaconNodeOptions/execution.ts b/packages/cli/src/options/beaconNodeOptions/execution.ts index f2f1b42fb2bf..85b81a5e708b 100644 --- a/packages/cli/src/options/beaconNodeOptions/execution.ts +++ b/packages/cli/src/options/beaconNodeOptions/execution.ts @@ -72,9 +72,9 @@ export const options: CliCommandOptions = { }, "execution.engineMock": { - description: "Set the execution engine to mock mode", + description: "Set the execution engine to mock mode (development only)", type: "boolean", - hidden: true, + hidden: false, group: "execution", }, From 5fe87f8e443e86511b8dae1acd0894226d6ae3fa Mon Sep 17 00:00:00 2001 From: twoeths Date: Tue, 2 Jul 2024 00:37:35 +0700 Subject: [PATCH 43/55] fix: prepareNextEpoch metric (#6924) * fix: prepareNextEpoch metric * Apply suggestions from code review --------- Co-authored-by: Cayman --- .../beacon-node/src/chain/prepareNextSlot.ts | 48 +++++++++---------- .../state-transition/src/stateTransition.ts | 1 + 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 3f730df3bf1d..48724ab25b0b 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -115,26 +115,6 @@ export class PrepareNextSlotScheduler { RegenCaller.precomputeEpoch ); - // assuming there is no reorg, it caches the checkpoint state & helps avoid doing a full state transition in the next slot - // + when gossip block comes, we need to validate and run state transition - // + if next slot is a skipped slot, it'd help getting target checkpoint state faster to validate attestations - if (isEpochTransition) { - this.metrics?.precomputeNextEpochTransition.count.inc({result: "success"}, 1); - const previousHits = this.chain.regen.updatePreComputedCheckpoint(headRoot, nextEpoch); - if (previousHits === 0) { - this.metrics?.precomputeNextEpochTransition.waste.inc(); - } - this.metrics?.precomputeNextEpochTransition.hits.set(previousHits ?? 0); - this.logger.verbose("Completed PrepareNextSlotScheduler epoch transition", { - nextEpoch, - headSlot, - prepareSlot, - previousHits, - }); - - precomputeEpochTransitionTimer?.(); - } - if (isExecutionStateType(prepareState)) { const proposerIndex = prepareState.epochCtx.getBeaconProposer(prepareSlot); const feeRecipient = this.chain.beaconProposerCache.get(proposerIndex); @@ -198,7 +178,7 @@ export class PrepareNextSlotScheduler { }); } - this.computeStateHashTreeRoot(updatedPrepareState); + this.computeStateHashTreeRoot(updatedPrepareState, isEpochTransition); // If emitPayloadAttributes is true emit a SSE payloadAttributes event if (this.chain.opts.emitPayloadAttributes === true) { @@ -213,7 +193,27 @@ export class PrepareNextSlotScheduler { this.chain.emitter.emit(routes.events.EventType.payloadAttributes, {data, version: fork}); } } else { - this.computeStateHashTreeRoot(prepareState); + this.computeStateHashTreeRoot(prepareState, isEpochTransition); + } + + // assuming there is no reorg, it caches the checkpoint state & helps avoid doing a full state transition in the next slot + // + when gossip block comes, we need to validate and run state transition + // + if next slot is a skipped slot, it'd help getting target checkpoint state faster to validate attestations + if (isEpochTransition) { + this.metrics?.precomputeNextEpochTransition.count.inc({result: "success"}, 1); + const previousHits = this.chain.regen.updatePreComputedCheckpoint(headRoot, nextEpoch); + if (previousHits === 0) { + this.metrics?.precomputeNextEpochTransition.waste.inc(); + } + this.metrics?.precomputeNextEpochTransition.hits.set(previousHits ?? 0); + this.logger.verbose("Completed PrepareNextSlotScheduler epoch transition", { + nextEpoch, + headSlot, + prepareSlot, + previousHits, + }); + + precomputeEpochTransitionTimer?.(); } } catch (e) { if (!isErrorAborted(e) && !isQueueErrorAborted(e)) { @@ -223,11 +223,11 @@ export class PrepareNextSlotScheduler { } }; - computeStateHashTreeRoot(state: CachedBeaconStateAllForks): void { + computeStateHashTreeRoot(state: CachedBeaconStateAllForks, isEpochTransition: boolean): void { // cache HashObjects for faster hashTreeRoot() later, especially for computeNewStateRoot() if we need to produce a block at slot 0 of epoch // see https://github.com/ChainSafe/lodestar/issues/6194 const hashTreeRootTimer = this.metrics?.stateHashTreeRootTime.startTimer({ - source: StateHashTreeRootSource.prepareNextSlot, + source: isEpochTransition ? StateHashTreeRootSource.prepareNextEpoch : StateHashTreeRootSource.prepareNextSlot, }); state.hashTreeRoot(); hashTreeRootTimer?.(); diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index 3ecd24ea9813..7602f4d9acc2 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -51,6 +51,7 @@ export enum StateHashTreeRootSource { stateTransition = "state_transition", blockTransition = "block_transition", prepareNextSlot = "prepare_next_slot", + prepareNextEpoch = "prepare_next_epoch", computeNewStateRoot = "compute_new_state_root", } From dbaa46e24a3d8f8dc889f9aac6fc73f31290d73e Mon Sep 17 00:00:00 2001 From: twoeths Date: Tue, 2 Jul 2024 20:19:48 +0700 Subject: [PATCH 44/55] chore: track Prepare Next Epoch heatmap (#6928) --- dashboards/lodestar_block_processor.json | 232 +++++++++++++++++++---- 1 file changed, 196 insertions(+), 36 deletions(-) diff --git a/dashboards/lodestar_block_processor.json b/dashboards/lodestar_block_processor.json index dca74c0180ee..e29d3522ce60 100644 --- a/dashboards/lodestar_block_processor.json +++ b/dashboards/lodestar_block_processor.json @@ -3738,6 +3738,166 @@ "title": "Prepare Next Epoch Duration", "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": 157 + }, + "id": 567, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_precompute_next_epoch_transition_duration_seconds_bucket[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Prepare Next Epoch Heatmap", + "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": 165 + }, + "id": 568, + "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": { + "mode": "single", + "showColorScale": false, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_stfn_hash_tree_root_seconds_bucket{source=\"prepare_next_epoch\"}[$rate_interval])", + "format": "heatmap", + "instant": false, + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Prepare Next Epoch hash_tree_root", + "type": "heatmap" + }, { "collapsed": false, "datasource": { @@ -3748,7 +3908,7 @@ "h": 1, "w": 24, "x": 0, - "y": 165 + "y": 173 }, "id": 136, "panels": [], @@ -3817,7 +3977,7 @@ "h": 8, "w": 12, "x": 0, - "y": 166 + "y": 174 }, "id": 130, "options": { @@ -3918,7 +4078,7 @@ "h": 8, "w": 12, "x": 12, - "y": 166 + "y": 174 }, "id": 140, "options": { @@ -4021,7 +4181,7 @@ "h": 8, "w": 12, "x": 0, - "y": 174 + "y": 182 }, "id": 132, "options": { @@ -4150,7 +4310,7 @@ "h": 8, "w": 12, "x": 12, - "y": 174 + "y": 182 }, "id": 138, "options": { @@ -4273,7 +4433,7 @@ "h": 8, "w": 12, "x": 0, - "y": 182 + "y": 190 }, "id": 531, "options": { @@ -4390,7 +4550,7 @@ "h": 8, "w": 12, "x": 12, - "y": 182 + "y": 190 }, "id": 533, "options": { @@ -4439,7 +4599,7 @@ "h": 1, "w": 24, "x": 0, - "y": 190 + "y": 198 }, "id": 538, "panels": [], @@ -4471,7 +4631,7 @@ "h": 8, "w": 12, "x": 0, - "y": 191 + "y": 199 }, "id": 539, "options": { @@ -4553,7 +4713,7 @@ "h": 8, "w": 12, "x": 12, - "y": 191 + "y": 199 }, "id": 540, "options": { @@ -4662,7 +4822,7 @@ "h": 8, "w": 12, "x": 0, - "y": 199 + "y": 207 }, "id": 553, "options": { @@ -4748,7 +4908,7 @@ "h": 8, "w": 12, "x": 12, - "y": 199 + "y": 207 }, "id": 554, "options": { @@ -4807,7 +4967,7 @@ "h": 8, "w": 12, "x": 0, - "y": 207 + "y": 215 }, "id": 556, "options": { @@ -4895,7 +5055,7 @@ "h": 8, "w": 12, "x": 12, - "y": 207 + "y": 215 }, "id": 542, "options": { @@ -5004,7 +5164,7 @@ "h": 8, "w": 12, "x": 0, - "y": 215 + "y": 223 }, "id": 541, "options": { @@ -5090,7 +5250,7 @@ "h": 8, "w": 12, "x": 12, - "y": 215 + "y": 223 }, "id": 555, "options": { @@ -5149,7 +5309,7 @@ "h": 8, "w": 12, "x": 0, - "y": 223 + "y": 231 }, "id": 557, "options": { @@ -5238,7 +5398,7 @@ "h": 8, "w": 12, "x": 12, - "y": 223 + "y": 231 }, "id": 544, "options": { @@ -5354,7 +5514,7 @@ "h": 8, "w": 12, "x": 0, - "y": 231 + "y": 239 }, "id": 543, "options": { @@ -5440,7 +5600,7 @@ "h": 8, "w": 12, "x": 12, - "y": 231 + "y": 239 }, "id": 558, "options": { @@ -5499,7 +5659,7 @@ "h": 8, "w": 12, "x": 0, - "y": 239 + "y": 247 }, "id": 559, "options": { @@ -5588,7 +5748,7 @@ "h": 8, "w": 12, "x": 12, - "y": 239 + "y": 247 }, "id": 546, "options": { @@ -5704,7 +5864,7 @@ "h": 8, "w": 12, "x": 0, - "y": 247 + "y": 255 }, "id": 545, "options": { @@ -5790,7 +5950,7 @@ "h": 8, "w": 12, "x": 12, - "y": 247 + "y": 255 }, "id": 560, "options": { @@ -5849,7 +6009,7 @@ "h": 8, "w": 12, "x": 0, - "y": 255 + "y": 263 }, "id": 561, "options": { @@ -5938,7 +6098,7 @@ "h": 8, "w": 12, "x": 12, - "y": 255 + "y": 263 }, "id": 548, "options": { @@ -6054,7 +6214,7 @@ "h": 8, "w": 12, "x": 0, - "y": 263 + "y": 271 }, "id": 547, "options": { @@ -6140,7 +6300,7 @@ "h": 8, "w": 12, "x": 12, - "y": 263 + "y": 271 }, "id": 562, "options": { @@ -6199,7 +6359,7 @@ "h": 8, "w": 12, "x": 0, - "y": 271 + "y": 279 }, "id": 563, "options": { @@ -6288,7 +6448,7 @@ "h": 8, "w": 12, "x": 12, - "y": 271 + "y": 279 }, "id": 550, "options": { @@ -6404,7 +6564,7 @@ "h": 8, "w": 12, "x": 0, - "y": 279 + "y": 287 }, "id": 549, "options": { @@ -6490,7 +6650,7 @@ "h": 8, "w": 12, "x": 12, - "y": 279 + "y": 287 }, "id": 564, "options": { @@ -6549,7 +6709,7 @@ "h": 8, "w": 12, "x": 0, - "y": 287 + "y": 295 }, "id": 565, "options": { @@ -6631,7 +6791,7 @@ "h": 8, "w": 12, "x": 12, - "y": 287 + "y": 295 }, "id": 552, "options": { @@ -6740,7 +6900,7 @@ "h": 8, "w": 12, "x": 0, - "y": 295 + "y": 303 }, "id": 551, "options": { @@ -6826,7 +6986,7 @@ "h": 8, "w": 12, "x": 12, - "y": 295 + "y": 303 }, "id": 566, "options": { From b6e20771c190f93465b3ea51bbd36f7e2e90ec21 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Fri, 5 Jul 2024 08:27:25 +0200 Subject: [PATCH 45/55] chore: update the flow for building bundle and running bundle tests (#6912) * Split bundle build process * Add build:bundle task * Add check-bundle task * Ignore bundle from lint * Add ignore pattern to config file --- .eslintrc.js | 3 +++ .github/actions/setup-and-build/action.yml | 8 ++++++++ package.json | 2 ++ packages/light-client/package.json | 13 +++++++------ packages/light-client/tsconfig.json | 2 +- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index cf6c3b95726c..46aca2515c83 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,9 @@ module.exports = { project: "./tsconfig.json", sourceType: "module", }, + ignorePatterns: [ + "webEsmBundle.browser.test.ts" + ], plugins: ["@typescript-eslint", "eslint-plugin-import", "@chainsafe/eslint-plugin-node", "prettier"], extends: [ "eslint:recommended", diff --git a/.github/actions/setup-and-build/action.yml b/.github/actions/setup-and-build/action.yml index ccb170d69fa3..de5bf89127e1 100644 --- a/.github/actions/setup-and-build/action.yml +++ b/.github/actions/setup-and-build/action.yml @@ -46,6 +46,14 @@ runs: shell: bash run: yarn check-build + - name: Build bundle + shell: bash + run: yarn build:bundle + + - name: Check bundle + shell: bash + run: yarn check-bundle + - name: Cache build artifacts uses: actions/cache@master with: diff --git a/package.json b/package.json index 02e285e0c86a..e4464b3b5a9f 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,14 @@ "clean": "rm -rf ./packages/*/lib ./packages/*/*.tsbuildinfo", "clean:nm": "rm -rf ./packages/*/node_modules ./node_modules", "build": "lerna run build", + "build:bundle": "lerna run build:bundle", "build:watch": "lerna exec --parallel -- 'yarn run build:watch'", "build:ifchanged": "lerna exec -- ../../scripts/build_if_changed.sh", "lint": "eslint --report-unused-disable-directives --color --ext .ts packages/*/src packages/*/test", "lint:fix": "yarn lint --fix", "lint-dashboards": "node scripts/lint-grafana-dashboards.mjs ./dashboards", "check-build": "lerna run check-build", + "check-bundle": "lerna run check-bundle", "check-readme": "lerna run check-readme", "check-types": "lerna run check-types", "check-spelling": "pyspelling -c .pyspelling.yml -v", diff --git a/packages/light-client/package.json b/packages/light-client/package.json index 4f79eeb61274..8d550941c1c5 100644 --- a/packages/light-client/package.json +++ b/packages/light-client/package.json @@ -54,20 +54,21 @@ "dist/**/*.d.ts" ], "scripts": { - "clean": "rm -rf lib && rm -f *.tsbuildinfo", + "clean": "rm -rf lib && rm -rf dist && rm -f *.tsbuildinfo", "build": "tsc -p tsconfig.build.json", - "build:dist": "vite build", "build:watch": "yarn run build --watch", - "build:release": "yarn clean && yarn run build && yarn run build:dist", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", - "check-types": "yarn run build:dist && tsc", + "build:bundle": "vite build", + "check-bundle": "node -e \"(async function() { await import('./dist/lightclient.min.mjs') })()\"", + "build:release": "yarn clean && yarn run build && yarn run build:bundle", + "check-types": "tsc", "lint": "eslint --color --ext .ts src/ test/", "lint:fix": "yarn run lint --fix", "test": "yarn test:unit", "test:unit": "vitest --run --dir test/unit/", "test:browsers": "yarn test:browsers:chrome && yarn test:browsers:firefox && yarn test:browsers:electron", - "test:browsers:chrome": "yarn run build:dist && vitest --run --browser chrome --config ./vitest.browser.config.ts --dir test/unit", - "test:browsers:firefox": "yarn run build:dist && vitest --run --browser firefox --config ./vitest.browser.config.ts --dir test/unit", + "test:browsers:chrome": "yarn run build:bundle && vitest --run --browser chrome --config ./vitest.browser.config.ts --dir test/unit", + "test:browsers:firefox": "yarn run build:bundle && vitest --run --browser firefox --config ./vitest.browser.config.ts --dir test/unit", "test:browsers:electron": "echo 'Electron tests will be introduced back in the future as soon vitest supports electron.'", "check-readme": "typescript-docs-verifier" }, diff --git a/packages/light-client/tsconfig.json b/packages/light-client/tsconfig.json index cfcb3522f506..63a72e23786c 100644 --- a/packages/light-client/tsconfig.json +++ b/packages/light-client/tsconfig.json @@ -1,5 +1,5 @@ { "extends": "../../tsconfig.json", - "exclude": ["src/index.browser.ts"], + "exclude": ["src/index.browser.ts", "test/unit/webEsmBundle.browser.test.ts"], "compilerOptions": {} } From 29d1c2231c6eaa17517860e24b146c647fb63622 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 5 Jul 2024 15:16:58 +0100 Subject: [PATCH 46/55] fix: eth1 provider error logging (#6863) --- .../src/eth1/provider/eth1Provider.ts | 4 ++-- packages/utils/src/waitFor.ts | 8 ++++++-- packages/utils/test/unit/waitFor.test.ts | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/beacon-node/src/eth1/provider/eth1Provider.ts b/packages/beacon-node/src/eth1/provider/eth1Provider.ts index d594c74a3abc..493884c0b5f0 100644 --- a/packages/beacon-node/src/eth1/provider/eth1Provider.ts +++ b/packages/beacon-node/src/eth1/provider/eth1Provider.ts @@ -91,7 +91,7 @@ export class Eth1Provider implements IEth1Provider { this.state = Eth1ProviderState.ONLINE; if (oldState !== Eth1ProviderState.ONLINE) { - this.logger?.info("Eth1Provider is back online", {oldState, newState: this.state}); + this.logger?.info("Eth1 provider is back online", {oldState, newState: this.state}); } }); @@ -109,7 +109,7 @@ export class Eth1Provider implements IEth1Provider { if (this.state !== Eth1ProviderState.ONLINE) { if (isOneMinutePassed()) { this.logger?.error( - "Eth1Provider faced error", + "Eth1 provider error", { state: this.state, lastErrorAt: new Date(Date.now() - isOneMinutePassed.msSinceLastCall).toLocaleTimeString(), diff --git a/packages/utils/src/waitFor.ts b/packages/utils/src/waitFor.ts index ea4fdf91b9ed..5aafa8c2b859 100644 --- a/packages/utils/src/waitFor.ts +++ b/packages/utils/src/waitFor.ts @@ -70,9 +70,13 @@ export function createElapsedTimeTracker({minElapsedTime}: {minElapsedTime: numb function elapsedTimeTracker(): boolean { const now = Date.now(); const msSinceLastCall = now - (lastTimeCalled ?? 0); - lastTimeCalled = now; - return msSinceLastCall > minElapsedTime; + if (msSinceLastCall > minElapsedTime) { + // Do not reset timer if called before timer elapsed + lastTimeCalled = now; + return true; + } + return false; } return Object.assign(elapsedTimeTracker, { diff --git a/packages/utils/test/unit/waitFor.test.ts b/packages/utils/test/unit/waitFor.test.ts index 293e5aba936a..412c06f888ed 100644 --- a/packages/utils/test/unit/waitFor.test.ts +++ b/packages/utils/test/unit/waitFor.test.ts @@ -36,8 +36,8 @@ describe("waitFor", () => { }); }); -describe("waitForElapsedTime", () => { - it("should true for the first time", () => { +describe("createElapsedTimeTracker", () => { + it("should return true for the first time", () => { const callIfTimePassed = createElapsedTimeTracker({minElapsedTime: 1000}); expect(callIfTimePassed()).toBe(true); @@ -52,6 +52,18 @@ describe("waitForElapsedTime", () => { expect(callIfTimePassed()).toBe(true); }); + it("should return true after the minElapsedTime has passed with intermediate calls", async () => { + const callIfTimePassed = createElapsedTimeTracker({minElapsedTime: 100}); + callIfTimePassed(); + + await sleep(75); + // Time has not elapsed yet but it should not reset timer + expect(callIfTimePassed()).toBe(false); + await sleep(75); + + expect(callIfTimePassed()).toBe(true); + }); + it("should return false before the minElapsedTime has passed", async () => { const callIfTimePassed = createElapsedTimeTracker({minElapsedTime: 100}); callIfTimePassed(); From 0c810b864d95ef4f832a17bc92f3e117c6310b67 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 5 Jul 2024 15:32:03 +0100 Subject: [PATCH 47/55] feat: add warning log to notifier if execution client is offline (#6919) * feat: add warning log to notifier if execution client is offline * refactor: convert execution engine state to a public property --- packages/beacon-node/src/execution/engine/disabled.ts | 6 ++---- packages/beacon-node/src/execution/engine/http.ts | 6 +----- .../beacon-node/src/execution/engine/interface.ts | 4 ++-- packages/beacon-node/src/node/notifier.ts | 11 ++++++++++- packages/beacon-node/src/sync/sync.ts | 2 +- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/beacon-node/src/execution/engine/disabled.ts b/packages/beacon-node/src/execution/engine/disabled.ts index 82bf84c37d33..68c72dc02ac6 100644 --- a/packages/beacon-node/src/execution/engine/disabled.ts +++ b/packages/beacon-node/src/execution/engine/disabled.ts @@ -1,6 +1,8 @@ import {ExecutionEngineState, IExecutionEngine, PayloadIdCache} from "./interface.js"; export class ExecutionEngineDisabled implements IExecutionEngine { + state = ExecutionEngineState.OFFLINE; + readonly payloadIdCache = new PayloadIdCache(); async notifyNewPayload(): Promise { @@ -26,8 +28,4 @@ export class ExecutionEngineDisabled implements IExecutionEngine { getPayloadBodiesByRange(): Promise { throw Error("Execution engine disabled"); } - - getState(): ExecutionEngineState { - throw Error("Execution engine disabled"); - } } diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index 0af956c87668..c57d5f9b2dbc 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -103,7 +103,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { // The default state is ONLINE, it will be updated to SYNCING once we receive the first payload // This assumption is better than the OFFLINE state, since we can't be sure if the EL is offline and being offline may trigger some notifications // It's safer to to avoid false positives and assume that the EL is syncing until we receive the first payload - private state: ExecutionEngineState = ExecutionEngineState.ONLINE; + state: ExecutionEngineState = ExecutionEngineState.ONLINE; readonly payloadIdCache = new PayloadIdCache(); /** @@ -417,10 +417,6 @@ export class ExecutionEngineHttp implements IExecutionEngine { return response.map(deserializeExecutionPayloadBody); } - getState(): ExecutionEngineState { - return this.state; - } - private updateEngineState(newState: ExecutionEngineState): void { const oldState = this.state; diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index b8f319700738..13575f01bce1 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -89,6 +89,8 @@ export type VersionedHashes = Uint8Array[]; * - Integrated code into the same binary */ export interface IExecutionEngine { + readonly state: ExecutionEngineState; + payloadIdCache: PayloadIdCache; /** * A state transition function which applies changes to the self.execution_state. @@ -146,6 +148,4 @@ export interface IExecutionEngine { getPayloadBodiesByHash(blockHash: DATA[]): Promise<(ExecutionPayloadBody | null)[]>; getPayloadBodiesByRange(start: number, count: number): Promise<(ExecutionPayloadBody | null)[]>; - - getState(): ExecutionEngineState; } diff --git a/packages/beacon-node/src/node/notifier.ts b/packages/beacon-node/src/node/notifier.ts index 19bc36d48dc2..6b9a29817158 100644 --- a/packages/beacon-node/src/node/notifier.ts +++ b/packages/beacon-node/src/node/notifier.ts @@ -1,10 +1,11 @@ import {BeaconConfig} from "@lodestar/config"; import {Epoch} from "@lodestar/types"; -import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; +import {CachedBeaconStateAllForks, computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {ProtoBlock, ExecutionStatus} from "@lodestar/fork-choice"; import {ErrorAborted, Logger, sleep, prettyBytes, prettyBytesShort} from "@lodestar/utils"; import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH} from "@lodestar/params"; import {computeEpochAtSlot, isExecutionCachedStateType, isMergeTransitionComplete} from "@lodestar/state-transition"; +import {ExecutionEngineState} from "../execution/index.js"; import {IBeaconChain} from "../chain/index.js"; import {INetwork} from "../network/index.js"; import {IBeaconSync, SyncState} from "../sync/index.js"; @@ -53,6 +54,14 @@ export async function runNodeNotifier(modules: NodeNotifierModules): Promise= config.BELLATRIX_FORK_EPOCH && + computeStartSlotAtEpoch(clockEpoch) === clockSlot && + chain.executionEngine.state === ExecutionEngineState.OFFLINE + ) { + logger.warn("Execution client is offline"); + } + const headInfo = chain.forkChoice.getHead(); const headState = chain.getHeadState(); const finalizedEpoch = headState.finalizedCheckpoint.epoch; diff --git a/packages/beacon-node/src/sync/sync.ts b/packages/beacon-node/src/sync/sync.ts index f7492c57da38..c7f01e1eae78 100644 --- a/packages/beacon-node/src/sync/sync.ts +++ b/packages/beacon-node/src/sync/sync.ts @@ -88,7 +88,7 @@ export class BeaconSync implements IBeaconSync { getSyncStatus(): SyncingStatus { const currentSlot = this.chain.clock.currentSlot; - const elOffline = this.chain.executionEngine.getState() === ExecutionEngineState.OFFLINE; + const elOffline = this.chain.executionEngine.state === ExecutionEngineState.OFFLINE; // If we are pre/at genesis, signal ready if (currentSlot <= GENESIS_SLOT) { From d56e871e775491d2bf8aa1b71e30a3582494872c Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 5 Jul 2024 15:33:30 +0100 Subject: [PATCH 48/55] fix: getHealth returns 206 status code if EL is optimistic or offline (#6920) * fix: getHealth returns 206 status code if EL is syncing / optimistic * Report 206 if EL is offline --- packages/beacon-node/src/api/impl/node/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/api/impl/node/index.ts b/packages/beacon-node/src/api/impl/node/index.ts index 103552ffb5b9..370bba9b3c79 100644 --- a/packages/beacon-node/src/api/impl/node/index.ts +++ b/packages/beacon-node/src/api/impl/node/index.ts @@ -71,7 +71,9 @@ export function getNodeApi( throw new ApiError(400, `Invalid syncing status code: ${syncingStatus}`); } - if (sync.getSyncStatus().isSyncing) { + const {isSyncing, isOptimistic, elOffline} = sync.getSyncStatus(); + + if (isSyncing || isOptimistic || elOffline) { // 206: Node is syncing but can serve incomplete data return {status: syncingStatus ?? routes.node.NodeHealth.SYNCING}; } else { From a335885a8cef3cf82130a1266a4475dd845b9b1e Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 5 Jul 2024 15:34:10 +0100 Subject: [PATCH 49/55] chore: update sepolia bootnodes (#6933) --- packages/cli/src/networks/sepolia.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/networks/sepolia.ts b/packages/cli/src/networks/sepolia.ts index b0ac965071a8..4a8729b785df 100644 --- a/packages/cli/src/networks/sepolia.ts +++ b/packages/cli/src/networks/sepolia.ts @@ -6,8 +6,10 @@ export const bootnodesFileUrl = "https://raw.githubusercontent.com/eth-clients/sepolia/main/metadata/bootstrap_nodes.txt"; export const bootEnrs = [ - "enr:-Iq4QMCTfIMXnow27baRUb35Q8iiFHSIDBJh6hQM5Axohhf4b6Kr_cOCu0htQ5WvVqKvFgY28893DHAg8gnBAXsAVqmGAX53x8JggmlkgnY0gmlwhLKAlv6Jc2VjcDI1NmsxoQK6S-Cii_KmfFdUJL2TANL3ksaKUnNXvTCv1tLwXs0QgIN1ZHCCIyk", - "enr:-Ly4QFoZTWR8ulxGVsWydTNGdwEESueIdj-wB6UmmjUcm-AOPxnQi7wprzwcdo7-1jBW_JxELlUKJdJES8TDsbl1EdNlh2F0dG5ldHOI__78_v2bsV-EZXRoMpA2-lATkAAAcf__________gmlkgnY0gmlwhBLYJjGJc2VjcDI1NmsxoQI0gujXac9rMAb48NtMqtSTyHIeNYlpjkbYpWJw46PmYYhzeW5jbmV0cw-DdGNwgiMog3VkcIIjKA", - "enr:-KG4QE5OIg5ThTjkzrlVF32WT_-XT14WeJtIz2zoTqLLjQhYAmJlnk4ItSoH41_2x0RX0wTFIe5GgjRzU2u7Q1fN4vADhGV0aDKQqP7o7pAAAHAyAAAAAAAAAIJpZIJ2NIJpcISlFsStiXNlY3AyNTZrMaEC-Rrd_bBZwhKpXzFCrStKp1q_HmGOewxY3KwM8ofAj_ODdGNwgiMog3VkcIIjKA", - "enr:-L64QC9Hhov4DhQ7mRukTOz4_jHm4DHlGL726NWH4ojH1wFgEwSin_6H95Gs6nW2fktTWbPachHJ6rUFu0iJNgA0SB2CARqHYXR0bmV0c4j__________4RldGgykDb6UBOQAABx__________-CaWSCdjSCaXCEA-2vzolzZWNwMjU2azGhA17lsUg60R776rauYMdrAz383UUgESoaHEzMkvm4K6k6iHN5bmNuZXRzD4N0Y3CCIyiDdWRwgiMo", + "enr:-KO4QP7MmB3juk8rUjJHcUoxZDU9Np4FlW0HyDEGIjSO7GD9PbSsabu7713cWSUWKDkxIypIXg1A-6lG7ySRGOMZHeGCAmuEZXRoMpDTH2GRkAAAc___________gmlkgnY0gmlwhBSoyGOJc2VjcDI1NmsxoQNta5b_bexSSwwrGW2Re24MjfMntzFd0f2SAxQtMj3ueYN0Y3CCIyiDdWRwgiMo", + "enr:-Ku4QDZ_rCowZFsozeWr60WwLgOfHzv1Fz2cuMvJqN5iJzLxKtVjoIURY42X_YTokMi3IGstW5v32uSYZyGUXj9Q_IECh2F0dG5ldHOIAAAAAAAAAACEZXRoMpCo_ujukAAAaf__________gmlkgnY0gmlwhIpEe5iJc2VjcDI1NmsxoQNHTpFdaNSCEWiN_QqT396nb0PzcUpLe3OVtLph-AciBYN1ZHCCIy0", + "enr:-Ku4QHRyRwEPT7s0XLYzJ_EeeWvZTXBQb4UCGy1F_3m-YtCNTtDlGsCMr4UTgo4uR89pv11uM-xq4w6GKfKhqU31hTgCh2F0dG5ldHOIAAAAAAAAAACEZXRoMpCo_ujukAAAaf__________gmlkgnY0gmlwhIrFM7WJc2VjcDI1NmsxoQI4diTwChN3zAAkarf7smOHCdFb1q3DSwdiQ_Lc_FdzFIN1ZHCCIy0", + "enr:-Ku4QOkvvf0u5Hg4-HhY-SJmEyft77G5h3rUM8VF_e-Hag5cAma3jtmFoX4WElLAqdILCA-UWFRN1ZCDJJVuEHrFeLkDh2F0dG5ldHOIAAAAAAAAAACEZXRoMpCo_ujukAAAaf__________gmlkgnY0gmlwhJK-AWeJc2VjcDI1NmsxoQLFcT5VE_NMiIC8Ll7GypWDnQ4UEmuzD7hF_Hf4veDJwIN1ZHCCIy0", + "enr:-Ku4QH6tYsHKITYeHUu5kdfXgEZWI18EWk_2RtGOn1jBPlx2UlS_uF3Pm5Dx7tnjOvla_zs-wwlPgjnEOcQDWXey51QCh2F0dG5ldHOIAAAAAAAAAACEZXRoMpCo_ujukAAAaf__________gmlkgnY0gmlwhIs7Mc6Jc2VjcDI1NmsxoQIET4Mlv9YzhrYhX_H9D7aWMemUrvki6W4J2Qo0YmFMp4N1ZHCCIy0", + "enr:-Ku4QDmz-4c1InchGitsgNk4qzorWMiFUoaPJT4G0IiF8r2UaevrekND1o7fdoftNucirj7sFFTTn2-JdC2Ej0p1Mn8Ch2F0dG5ldHOIAAAAAAAAAACEZXRoMpCo_ujukAAAaf__________gmlkgnY0gmlwhKpA-liJc2VjcDI1NmsxoQMpHP5U1DK8O_JQU6FadmWbE42qEdcGlllR8HcSkkfWq4N1ZHCCIy0", ]; From 1aba391b1b329e6864f20b0fbd69e4d63572a5bb Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 5 Jul 2024 15:38:52 +0100 Subject: [PATCH 50/55] feat: warning log if primary beacon node is unhealthy (#6921) * feat: add warning log if primary beacon node is unhealthy * Fix api client stub type * Improve http client stub * Update all api client stubs * Only sanitize primary node url once --- packages/api/src/beacon/client/index.ts | 3 ++- packages/api/src/utils/client/httpClient.ts | 5 +++-- .../beacon-node/test/utils/node/validator.ts | 12 +++++++++++- packages/validator/src/validator.ts | 15 +++++++++++++++ packages/validator/test/utils/apiStub.ts | 18 +++++++++++++++--- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/api/src/beacon/client/index.ts b/packages/api/src/beacon/client/index.ts index 6512d23673c5..6622fe421cd2 100644 --- a/packages/api/src/beacon/client/index.ts +++ b/packages/api/src/beacon/client/index.ts @@ -23,7 +23,7 @@ type ClientModules = HttpClientModules & { httpClient?: IHttpClient; }; -export type ApiClient = {[K in keyof Endpoints]: ApiClientMethods}; +export type ApiClient = {[K in keyof Endpoints]: ApiClientMethods} & {httpClient: IHttpClient}; /** * REST HTTP client for all routes @@ -42,5 +42,6 @@ export function getClient(opts: HttpClientOptions, modules: ClientModules): ApiC node: node.getClient(config, httpClient), proof: proof.getClient(config, httpClient), validator: validator.getClient(config, httpClient), + httpClient, }; } diff --git a/packages/api/src/utils/client/httpClient.ts b/packages/api/src/utils/client/httpClient.ts index eeccd59d2032..070f6d4fce93 100644 --- a/packages/api/src/utils/client/httpClient.ts +++ b/packages/api/src/utils/client/httpClient.ts @@ -50,6 +50,8 @@ export const defaultInit: Required = { export interface IHttpClient { readonly baseUrl: string; + readonly urlsInits: UrlInitRequired[]; + readonly urlsScore: number[]; request( definition: RouteDefinitionExtra, @@ -71,14 +73,13 @@ export type HttpClientModules = { export class HttpClient implements IHttpClient { readonly urlsInits: UrlInitRequired[] = []; + readonly urlsScore: number[]; private readonly signal: null | AbortSignal; private readonly fetch: typeof fetch; private readonly metrics: null | Metrics; private readonly logger: null | Logger; - private readonly urlsScore: number[]; - /** * Cache to keep track of routes per server that do not support SSZ. This cache will only be * populated if we receive a 415 error response from the server after sending a SSZ request body. diff --git a/packages/beacon-node/test/utils/node/validator.ts b/packages/beacon-node/test/utils/node/validator.ts index c686449a29f2..c9a705ae3dc1 100644 --- a/packages/beacon-node/test/utils/node/validator.ts +++ b/packages/beacon-node/test/utils/node/validator.ts @@ -1,4 +1,5 @@ import tmp from "tmp"; +import {vi} from "vitest"; import type {SecretKey} from "@chainsafe/bls/types"; import {LevelDbController} from "@lodestar/db"; import {interopSecretKey} from "@lodestar/state-transition"; @@ -91,7 +92,7 @@ export async function getAndInitDevValidators({ } export function getApiFromServerHandlers(api: BeaconApiMethods): ApiClient { - return mapValues(api, (apiModule) => + const apiClient = mapValues(api, (apiModule) => mapValues(apiModule, (api: (args: unknown, context: unknown) => PromiseLike<{data: unknown; meta: unknown}>) => { return async (args: unknown) => { try { @@ -114,6 +115,15 @@ export function getApiFromServerHandlers(api: BeaconApiMethods): ApiClient { }; }) ) as ApiClient; + + apiClient.httpClient = { + baseUrl: "", + request: vi.fn(), + urlsInits: [], + urlsScore: [], + }; + + return apiClient; } export function getNodeApiUrl(node: BeaconNode): string { diff --git a/packages/validator/src/validator.ts b/packages/validator/src/validator.ts index 9cb9f2e2d840..706cf7410b43 100644 --- a/packages/validator/src/validator.ts +++ b/packages/validator/src/validator.ts @@ -134,6 +134,21 @@ export class Validator { this.clock.start(this.controller.signal); this.chainHeaderTracker.start(this.controller.signal); + // Add notifier to warn user if primary node is unhealthy as there might + // not be any errors in the logs due to fallback nodes handling the requests + const {httpClient} = this.api; + if (httpClient.urlsInits.length > 1) { + const primaryNodeUrl = toSafePrintableUrl(httpClient.urlsInits[0].baseUrl); + + this.clock?.runEveryEpoch(async () => { + // Only emit warning if URL score is 0 to prevent false positives + // if just a single request fails which might happen due to other reasons + if (httpClient.urlsScore[0] === 0) { + this.logger?.warn("Primary beacon node is unhealthy", {url: primaryNodeUrl}); + } + }); + } + if (metrics) { this.db.setMetrics(metrics.db); diff --git a/packages/validator/test/utils/apiStub.ts b/packages/validator/test/utils/apiStub.ts index 0ee39662952f..ac41c7145128 100644 --- a/packages/validator/test/utils/apiStub.ts +++ b/packages/validator/test/utils/apiStub.ts @@ -1,7 +1,18 @@ import {vi, Mocked} from "vitest"; -import {ApiClientMethods, ApiResponse, Endpoint, Endpoints, HttpStatusCode} from "@lodestar/api"; +import {ApiClientMethods, ApiResponse, Endpoint, Endpoints, HttpStatusCode, IHttpClient} from "@lodestar/api"; -export function getApiClientStub(): {[K in keyof Endpoints]: Mocked>} { +type ApiClientStub = {[K in keyof Endpoints]: Mocked>} & { + httpClient: Mocked; +}; + +const httpClientStub: IHttpClient = { + baseUrl: "", + request: vi.fn(), + urlsInits: [], + urlsScore: [], +}; + +export function getApiClientStub(): ApiClientStub { return { beacon: { getStateValidators: vi.fn(), @@ -25,7 +36,8 @@ export function getApiClientStub(): {[K in keyof Endpoints]: Mocked>}; + httpClient: httpClientStub, + } as unknown as ApiClientStub; } export function mockApiResponse>({ From 0cb3998a2a3837068d83d96312e9938a4404c8bf Mon Sep 17 00:00:00 2001 From: NC Date: Fri, 5 Jul 2024 17:54:13 +0300 Subject: [PATCH 51/55] feat: add proposer boost reorg related grafana panels (#6910) * Add no-reorg reason panel * Add weak block panel --- dashboards/lodestar_block_production.json | 172 +++++++++++++++++++++- 1 file changed, 169 insertions(+), 3 deletions(-) diff --git a/dashboards/lodestar_block_production.json b/dashboards/lodestar_block_production.json index 96ab44c6a550..5e33a72ef9af 100644 --- a/dashboards/lodestar_block_production.json +++ b/dashboards/lodestar_block_production.json @@ -1672,13 +1672,179 @@ "title": "Request Next Epoch Proposal Duties", "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": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 54 + }, + "id": 548, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_fork_choice_not_reorged_reason_total[$rate_interval])", + "hide": false, + "instant": false, + "legendFormat": "{{reason}}", + "range": true, + "refId": "A" + } + ], + "title": "Reason of building on head block", + "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": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 56 + }, + "id": 549, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(beacon_weak_head_detected_total[$rate_interval])", + "hide": false, + "instant": false, + "legendFormat": "weak head", + "range": true, + "refId": "A" + } + ], + "title": "Weak head blocks", + "type": "timeseries" + }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, - "y": 56 + "y": 64 }, "id": 541, "panels": [], @@ -1709,7 +1875,7 @@ "h": 8, "w": 12, "x": 0, - "y": 57 + "y": 65 }, "id": 543, "options": { @@ -1787,7 +1953,7 @@ "h": 8, "w": 12, "x": 12, - "y": 57 + "y": 65 }, "id": 545, "options": { From dc5b68d9843e07227739b5b49af51238922381a6 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 5 Jul 2024 19:13:49 +0100 Subject: [PATCH 52/55] chore: reduce call stack in http client (#6934) * chore: reduce call stack in http client * Fix binding * Add private method to get request method * Pass init to request method getter --- packages/api/src/utils/client/httpClient.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/api/src/utils/client/httpClient.ts b/packages/api/src/utils/client/httpClient.ts index 070f6d4fce93..27757aa0846c 100644 --- a/packages/api/src/utils/client/httpClient.ts +++ b/packages/api/src/utils/client/httpClient.ts @@ -151,7 +151,7 @@ export class HttpClient implements IHttpClient { if (init.retries > 0) { return this.requestWithRetries(definition, args, init); } else { - return this.requestFallbackToJson(definition, args, init); + return this.getRequestMethod(init)(definition, args, init); } } else { return this.requestWithFallbacks(definition, args, localInit); @@ -202,7 +202,7 @@ export class HttpClient implements IHttpClient { } const init = mergeInits(definition, urlInit, localInit); - const requestMethod = (init.retries > 0 ? this.requestWithRetries : this.requestFallbackToJson).bind(this); + const requestMethod = init.retries > 0 ? this.requestWithRetries.bind(this) : this.getRequestMethod(init); requestMethod(definition, args, init).then( async (res) => { @@ -273,10 +273,11 @@ export class HttpClient implements IHttpClient { ): Promise> { const {retries, retryDelay, signal} = init; const routeId = definition.operationId; + const requestMethod = this.getRequestMethod(init); return retry( async (attempt) => { - const res = await this.requestFallbackToJson(definition, args, init); + const res = await requestMethod(definition, args, init); if (!res.ok && attempt <= retries) { throw res.error(); } @@ -395,6 +396,10 @@ export class HttpClient implements IHttpClient { abortSignals.forEach((s) => s?.removeEventListener("abort", onSignalAbort)); } } + + private getRequestMethod(init: ApiRequestInitRequired): typeof this._request { + return init.requestWireFormat === WireFormat.ssz ? this.requestFallbackToJson.bind(this) : this._request.bind(this); + } } function mergeInits( From 9b728eda63ee8a77c2fd48191b47382061295caa Mon Sep 17 00:00:00 2001 From: Faith Tosin Olapade <45565979+Faithtosin@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:18:05 +0200 Subject: [PATCH 53/55] chore: update sepolia.ts to include Lodestar bootnode (#6936) add new holesky enr --- packages/cli/src/networks/sepolia.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/networks/sepolia.ts b/packages/cli/src/networks/sepolia.ts index 4a8729b785df..9dfd5dc20a0f 100644 --- a/packages/cli/src/networks/sepolia.ts +++ b/packages/cli/src/networks/sepolia.ts @@ -12,4 +12,5 @@ export const bootEnrs = [ "enr:-Ku4QOkvvf0u5Hg4-HhY-SJmEyft77G5h3rUM8VF_e-Hag5cAma3jtmFoX4WElLAqdILCA-UWFRN1ZCDJJVuEHrFeLkDh2F0dG5ldHOIAAAAAAAAAACEZXRoMpCo_ujukAAAaf__________gmlkgnY0gmlwhJK-AWeJc2VjcDI1NmsxoQLFcT5VE_NMiIC8Ll7GypWDnQ4UEmuzD7hF_Hf4veDJwIN1ZHCCIy0", "enr:-Ku4QH6tYsHKITYeHUu5kdfXgEZWI18EWk_2RtGOn1jBPlx2UlS_uF3Pm5Dx7tnjOvla_zs-wwlPgjnEOcQDWXey51QCh2F0dG5ldHOIAAAAAAAAAACEZXRoMpCo_ujukAAAaf__________gmlkgnY0gmlwhIs7Mc6Jc2VjcDI1NmsxoQIET4Mlv9YzhrYhX_H9D7aWMemUrvki6W4J2Qo0YmFMp4N1ZHCCIy0", "enr:-Ku4QDmz-4c1InchGitsgNk4qzorWMiFUoaPJT4G0IiF8r2UaevrekND1o7fdoftNucirj7sFFTTn2-JdC2Ej0p1Mn8Ch2F0dG5ldHOIAAAAAAAAAACEZXRoMpCo_ujukAAAaf__________gmlkgnY0gmlwhKpA-liJc2VjcDI1NmsxoQMpHP5U1DK8O_JQU6FadmWbE42qEdcGlllR8HcSkkfWq4N1ZHCCIy0", + "enr:-KG4QJejf8KVtMeAPWFhN_P0c4efuwu1pZHELTveiXUeim6nKYcYcMIQpGxxdgT2Xp9h-M5pr9gn2NbbwEAtxzu50Y8BgmlkgnY0gmlwhEEVkQCDaXA2kCoBBPnAEJg4AAAAAAAAAAGJc2VjcDI1NmsxoQLEh_eVvk07AQABvLkTGBQTrrIOQkzouMgSBtNHIRUxOIN1ZHCCIyiEdWRwNoIjKA", ]; From b49beacb42b28325dbb211e1b50c361772320deb Mon Sep 17 00:00:00 2001 From: Phil Ngo Date: Tue, 9 Jul 2024 11:54:24 -0400 Subject: [PATCH 54/55] chore: bump package versions to 1.20.0 --- lerna.json | 2 +- packages/api/package.json | 10 +++++----- packages/beacon-node/package.json | 26 +++++++++++++------------- packages/cli/package.json | 26 +++++++++++++------------- packages/config/package.json | 6 +++--- packages/db/package.json | 8 ++++---- packages/flare/package.json | 14 +++++++------- packages/fork-choice/package.json | 12 ++++++------ packages/light-client/package.json | 14 +++++++------- packages/logger/package.json | 6 +++--- packages/params/package.json | 2 +- packages/prover/package.json | 18 +++++++++--------- packages/reqresp/package.json | 12 ++++++------ packages/spec-test-util/package.json | 4 ++-- packages/state-transition/package.json | 10 +++++----- packages/test-utils/package.json | 6 +++--- packages/types/package.json | 4 ++-- packages/utils/package.json | 2 +- packages/validator/package.json | 18 +++++++++--------- 19 files changed, 100 insertions(+), 100 deletions(-) diff --git a/lerna.json b/lerna.json index 582c64a0d681..2f8525637607 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useNx": true, - "version": "1.19.0", + "version": "1.20.0", "stream": true, "command": { "version": { diff --git a/packages/api/package.json b/packages/api/package.json index cd989ec0003f..d5f261aa284a 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.19.0", + "version": "1.20.0", "type": "module", "exports": { ".": { @@ -72,10 +72,10 @@ "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.7.1", "@chainsafe/ssz": "^0.15.1", - "@lodestar/config": "^1.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/types": "^1.19.0", - "@lodestar/utils": "^1.19.0", + "@lodestar/config": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/types": "^1.20.0", + "@lodestar/utils": "^1.20.0", "eventsource": "^2.0.2", "qs": "^6.11.1" }, diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index ee17ff0c0c08..3b93116d8737 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.19.0", + "version": "1.20.0", "type": "module", "exports": { ".": { @@ -120,18 +120,18 @@ "@libp2p/peer-id-factory": "^4.1.0", "@libp2p/prometheus-metrics": "^3.0.21", "@libp2p/tcp": "9.0.23", - "@lodestar/api": "^1.19.0", - "@lodestar/config": "^1.19.0", - "@lodestar/db": "^1.19.0", - "@lodestar/fork-choice": "^1.19.0", - "@lodestar/light-client": "^1.19.0", - "@lodestar/logger": "^1.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/reqresp": "^1.19.0", - "@lodestar/state-transition": "^1.19.0", - "@lodestar/types": "^1.19.0", - "@lodestar/utils": "^1.19.0", - "@lodestar/validator": "^1.19.0", + "@lodestar/api": "^1.20.0", + "@lodestar/config": "^1.20.0", + "@lodestar/db": "^1.20.0", + "@lodestar/fork-choice": "^1.20.0", + "@lodestar/light-client": "^1.20.0", + "@lodestar/logger": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/reqresp": "^1.20.0", + "@lodestar/state-transition": "^1.20.0", + "@lodestar/types": "^1.20.0", + "@lodestar/utils": "^1.20.0", + "@lodestar/validator": "^1.20.0", "@multiformats/multiaddr": "^12.1.3", "c-kzg": "^2.1.2", "datastore-core": "^9.1.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 6a2f9d04644d..088e0e8b636e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@chainsafe/lodestar", - "version": "1.19.0", + "version": "1.20.0", "description": "Command line interface for lodestar", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -63,17 +63,17 @@ "@libp2p/crypto": "^4.1.0", "@libp2p/peer-id": "^4.1.0", "@libp2p/peer-id-factory": "^4.1.0", - "@lodestar/api": "^1.19.0", - "@lodestar/beacon-node": "^1.19.0", - "@lodestar/config": "^1.19.0", - "@lodestar/db": "^1.19.0", - "@lodestar/light-client": "^1.19.0", - "@lodestar/logger": "^1.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/state-transition": "^1.19.0", - "@lodestar/types": "^1.19.0", - "@lodestar/utils": "^1.19.0", - "@lodestar/validator": "^1.19.0", + "@lodestar/api": "^1.20.0", + "@lodestar/beacon-node": "^1.20.0", + "@lodestar/config": "^1.20.0", + "@lodestar/db": "^1.20.0", + "@lodestar/light-client": "^1.20.0", + "@lodestar/logger": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/state-transition": "^1.20.0", + "@lodestar/types": "^1.20.0", + "@lodestar/utils": "^1.20.0", + "@lodestar/validator": "^1.20.0", "@multiformats/multiaddr": "^12.1.3", "deepmerge": "^4.3.1", "ethers": "^6.7.0", @@ -89,7 +89,7 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.19.0", + "@lodestar/test-utils": "^1.20.0", "@types/debug": "^4.1.7", "@types/got": "^9.6.12", "@types/inquirer": "^9.0.3", diff --git a/packages/config/package.json b/packages/config/package.json index bc8f880cb7a8..b773ae3e0eb1 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/config", - "version": "1.19.0", + "version": "1.20.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.19.0", - "@lodestar/types": "^1.19.0" + "@lodestar/params": "^1.20.0", + "@lodestar/types": "^1.20.0" } } diff --git a/packages/db/package.json b/packages/db/package.json index 2ff5421c7ea9..4f496533a1fe 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/db", - "version": "1.19.0", + "version": "1.20.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.19.0", - "@lodestar/utils": "^1.19.0", + "@lodestar/config": "^1.20.0", + "@lodestar/utils": "^1.20.0", "classic-level": "^1.4.1", "it-all": "^3.0.4" }, "devDependencies": { - "@lodestar/logger": "^1.19.0" + "@lodestar/logger": "^1.20.0" } } diff --git a/packages/flare/package.json b/packages/flare/package.json index 54a0dbec51f5..e464d2bd9f2b 100644 --- a/packages/flare/package.json +++ b/packages/flare/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/flare", - "version": "1.19.0", + "version": "1.20.0", "description": "Beacon chain debugging tool", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -60,12 +60,12 @@ "dependencies": { "@chainsafe/bls": "7.1.3", "@chainsafe/bls-keygen": "^0.4.0", - "@lodestar/api": "^1.19.0", - "@lodestar/config": "^1.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/state-transition": "^1.19.0", - "@lodestar/types": "^1.19.0", - "@lodestar/utils": "^1.19.0", + "@lodestar/api": "^1.20.0", + "@lodestar/config": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/state-transition": "^1.20.0", + "@lodestar/types": "^1.20.0", + "@lodestar/utils": "^1.20.0", "source-map-support": "^0.5.21", "yargs": "^17.7.1" }, diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index 1a1b20bfe6e1..43b67d179ecd 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.19.0", + "version": "1.20.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.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/state-transition": "^1.19.0", - "@lodestar/types": "^1.19.0", - "@lodestar/utils": "^1.19.0" + "@lodestar/config": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/state-transition": "^1.20.0", + "@lodestar/types": "^1.20.0", + "@lodestar/utils": "^1.20.0" }, "keywords": [ "ethereum", diff --git a/packages/light-client/package.json b/packages/light-client/package.json index 8d550941c1c5..4ef321e271ce 100644 --- a/packages/light-client/package.json +++ b/packages/light-client/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.19.0", + "version": "1.20.0", "type": "module", "exports": { ".": { @@ -59,7 +59,7 @@ "build:watch": "yarn run build --watch", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "build:bundle": "vite build", - "check-bundle": "node -e \"(async function() { await import('./dist/lightclient.min.mjs') })()\"", + "check-bundle": "node -e \"(async function() { await import('./dist/lightclient.min.mjs') })()\"", "build:release": "yarn clean && yarn run build && yarn run build:bundle", "check-types": "tsc", "lint": "eslint --color --ext .ts src/ test/", @@ -76,11 +76,11 @@ "@chainsafe/bls": "7.1.3", "@chainsafe/persistent-merkle-tree": "^0.7.1", "@chainsafe/ssz": "^0.15.1", - "@lodestar/api": "^1.19.0", - "@lodestar/config": "^1.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/types": "^1.19.0", - "@lodestar/utils": "^1.19.0", + "@lodestar/api": "^1.20.0", + "@lodestar/config": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/types": "^1.20.0", + "@lodestar/utils": "^1.20.0", "mitt": "^3.0.0" }, "devDependencies": { diff --git a/packages/logger/package.json b/packages/logger/package.json index 71c7623fb028..e8f3554a194d 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.19.0", + "version": "1.20.0", "type": "module", "exports": { ".": { @@ -66,14 +66,14 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@lodestar/utils": "^1.19.0", + "@lodestar/utils": "^1.20.0", "winston": "^3.8.2", "winston-daily-rotate-file": "^4.7.1", "winston-transport": "^4.5.0" }, "devDependencies": { "@chainsafe/threads": "^1.11.1", - "@lodestar/test-utils": "^1.19.0", + "@lodestar/test-utils": "^1.20.0", "@types/triple-beam": "^1.3.2", "triple-beam": "^1.3.0" }, diff --git a/packages/params/package.json b/packages/params/package.json index 758e62feb178..dcaf86cb9ef3 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/params", - "version": "1.19.0", + "version": "1.20.0", "description": "Chain parameters required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", diff --git a/packages/prover/package.json b/packages/prover/package.json index 9d4cb905e59b..59accc218e64 100644 --- a/packages/prover/package.json +++ b/packages/prover/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.19.0", + "version": "1.20.0", "type": "module", "exports": { ".": { @@ -69,13 +69,13 @@ "@ethereumjs/tx": "^4.1.2", "@ethereumjs/util": "^8.0.6", "@ethereumjs/vm": "^6.4.2", - "@lodestar/api": "^1.19.0", - "@lodestar/config": "^1.19.0", - "@lodestar/light-client": "^1.19.0", - "@lodestar/logger": "^1.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/types": "^1.19.0", - "@lodestar/utils": "^1.19.0", + "@lodestar/api": "^1.20.0", + "@lodestar/config": "^1.20.0", + "@lodestar/light-client": "^1.20.0", + "@lodestar/logger": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/types": "^1.20.0", + "@lodestar/utils": "^1.20.0", "ethereum-cryptography": "^2.0.0", "find-up": "^6.3.0", "http-proxy": "^1.18.1", @@ -84,7 +84,7 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.19.0", + "@lodestar/test-utils": "^1.20.0", "@types/http-proxy": "^1.17.10", "@types/yargs": "^17.0.24", "axios": "^1.3.4", diff --git a/packages/reqresp/package.json b/packages/reqresp/package.json index 6e671c3ade82..9540d8a4cfd4 100644 --- a/packages/reqresp/package.json +++ b/packages/reqresp/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.19.0", + "version": "1.20.0", "type": "module", "exports": { ".": { @@ -54,9 +54,9 @@ "dependencies": { "@chainsafe/fast-crc32c": "^4.1.1", "@libp2p/interface": "^1.3.0", - "@lodestar/config": "^1.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/utils": "^1.19.0", + "@lodestar/config": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/utils": "^1.20.0", "it-all": "^3.0.4", "it-pipe": "^3.0.1", "snappy": "^7.2.2", @@ -65,8 +65,8 @@ "uint8arraylist": "^2.4.7" }, "devDependencies": { - "@lodestar/logger": "^1.19.0", - "@lodestar/types": "^1.19.0", + "@lodestar/logger": "^1.20.0", + "@lodestar/types": "^1.20.0", "libp2p": "1.4.3" }, "peerDependencies": { diff --git a/packages/spec-test-util/package.json b/packages/spec-test-util/package.json index f092a26fc8b0..248dce2eae3c 100644 --- a/packages/spec-test-util/package.json +++ b/packages/spec-test-util/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/spec-test-util", - "version": "1.19.0", + "version": "1.20.0", "description": "Spec test suite generator from yaml test files", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -62,7 +62,7 @@ "blockchain" ], "dependencies": { - "@lodestar/utils": "^1.19.0", + "@lodestar/utils": "^1.20.0", "axios": "^1.3.4", "rimraf": "^4.4.1", "snappyjs": "^0.7.0", diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index 76b7f52325a3..4a5f97807e80 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.19.0", + "version": "1.20.0", "type": "module", "exports": { ".": { @@ -64,10 +64,10 @@ "@chainsafe/persistent-merkle-tree": "^0.7.1", "@chainsafe/persistent-ts": "^0.19.1", "@chainsafe/ssz": "^0.15.1", - "@lodestar/config": "^1.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/types": "^1.19.0", - "@lodestar/utils": "^1.19.0", + "@lodestar/config": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/types": "^1.20.0", + "@lodestar/utils": "^1.20.0", "bigint-buffer": "^1.1.5" }, "keywords": [ diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 1a39f4d6c7ca..ff913236d09c 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@lodestar/test-utils", "private": true, - "version": "1.19.0", + "version": "1.20.0", "description": "Test utilities reused across other packages", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -59,8 +59,8 @@ "dependencies": { "@chainsafe/bls": "7.1.3", "@chainsafe/bls-keystore": "^3.0.1", - "@lodestar/params": "^1.19.0", - "@lodestar/utils": "^1.19.0", + "@lodestar/params": "^1.20.0", + "@lodestar/utils": "^1.20.0", "axios": "^1.3.4", "testcontainers": "^10.2.1", "tmp": "^0.2.1", diff --git a/packages/types/package.json b/packages/types/package.json index 037a3c46d71b..2918c3f6547f 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.19.0", + "version": "1.20.0", "type": "module", "exports": { ".": { @@ -71,7 +71,7 @@ "types": "lib/index.d.ts", "dependencies": { "@chainsafe/ssz": "^0.15.1", - "@lodestar/params": "^1.19.0", + "@lodestar/params": "^1.20.0", "ethereum-cryptography": "^2.0.0" }, "keywords": [ diff --git a/packages/utils/package.json b/packages/utils/package.json index a0c46bb4994d..01aa35d7b990 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.19.0", + "version": "1.20.0", "type": "module", "exports": "./lib/index.js", "files": [ diff --git a/packages/validator/package.json b/packages/validator/package.json index 7bd1cdae0ac7..3c39e2f63bf1 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/validator", - "version": "1.19.0", + "version": "1.20.0", "description": "A Typescript implementation of the validator client", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -47,17 +47,17 @@ "dependencies": { "@chainsafe/bls": "7.1.3", "@chainsafe/ssz": "^0.15.1", - "@lodestar/api": "^1.19.0", - "@lodestar/config": "^1.19.0", - "@lodestar/db": "^1.19.0", - "@lodestar/params": "^1.19.0", - "@lodestar/state-transition": "^1.19.0", - "@lodestar/types": "^1.19.0", - "@lodestar/utils": "^1.19.0", + "@lodestar/api": "^1.20.0", + "@lodestar/config": "^1.20.0", + "@lodestar/db": "^1.20.0", + "@lodestar/params": "^1.20.0", + "@lodestar/state-transition": "^1.20.0", + "@lodestar/types": "^1.20.0", + "@lodestar/utils": "^1.20.0", "strict-event-emitter-types": "^2.0.0" }, "devDependencies": { - "@lodestar/test-utils": "^1.19.0", + "@lodestar/test-utils": "^1.20.0", "bigint-buffer": "^1.1.5", "rimraf": "^4.4.1" } From 539fcc5a25c516d4b7d1cf4a20d40ff329752cb7 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 9 Jul 2024 22:31:35 +0100 Subject: [PATCH 55/55] docs: fix search, redirects and broken links (#6940) --- CONTRIBUTING.md | 2 +- docs/package.json | 12 +- docs/sidebars.ts | 5 + docs/yarn.lock | 1861 +++++-------------------------------- packages/prover/README.md | 2 +- 5 files changed, 241 insertions(+), 1641 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 903f9ee32da7..9592f3bb0237 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,7 +50,7 @@ Contributing to tests: ## Devcontainer -A [devcontainer](https://containers.dev/) [configuration](.devcontainer/devcontainer.json) is provided to help speed up linux based development environment setup. It will be used by [GitHub Codespaces](https://github.com/features/codespaces) or directly inside VS Code via your local through this [extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). +A [devcontainer](https://containers.dev/) [configuration](https://github.com/ChainSafe/lodestar/blob/unstable/.devcontainer/devcontainer.json) is provided to help speed up linux based development environment setup. It will be used by [GitHub Codespaces](https://github.com/features/codespaces) or directly inside VS Code via your local through this [extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). ### Common Issues diff --git a/docs/package.json b/docs/package.json index 6159ba123215..bf1495c9b657 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,10 +14,10 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "^3.4.0", - "@docusaurus/plugin-client-redirects": "^3.4.0", - "@docusaurus/preset-classic": "^3.4.0", - "@docusaurus/theme-mermaid": "^3.4.0", + "@docusaurus/core": "3.2.0", + "@docusaurus/plugin-client-redirects": "3.2.0", + "@docusaurus/preset-classic": "3.2.0", + "@docusaurus/theme-mermaid": "3.2.0", "@easyops-cn/docusaurus-search-local": "^0.40.1", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", @@ -26,8 +26,8 @@ "react-dom": "^18.0.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.4.0", - "@docusaurus/types": "^3.4.0" + "@docusaurus/module-type-aliases": "3.2.0", + "@docusaurus/types": "3.2.0" }, "browserslist": { "production": [ diff --git a/docs/sidebars.ts b/docs/sidebars.ts index a82421a961cb..16bd9586d1a1 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -69,6 +69,11 @@ const sidebars: SidebarsConfig = { label: "Contributing", collapsed: false, items: [ + { + type: "doc", + label: "Getting Started", + id: "contribution/getting-started", + }, { type: "category", label: "Advanced Topics", diff --git a/docs/yarn.lock b/docs/yarn.lock index a2c90a3ace6c..41966c013183 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -154,24 +154,11 @@ "@babel/highlight" "^7.23.4" chalk "^2.4.2" -"@babel/code-frame@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" - integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== - dependencies: - "@babel/highlight" "^7.24.7" - picocolors "^1.0.0" - "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== -"@babel/compat-data@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" - integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== - "@babel/core@^7.19.6", "@babel/core@^7.23.3": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.0.tgz#56cbda6b185ae9d9bed369816a8f4423c5f2ff1b" @@ -193,27 +180,6 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/core@^7.21.3": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" - integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.7" - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helpers" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - "@babel/generator@^7.23.3", "@babel/generator@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" @@ -224,16 +190,6 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/generator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" - integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== - dependencies: - "@babel/types" "^7.24.7" - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" - "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" @@ -241,13 +197,6 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-annotate-as-pure@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" - integrity sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg== - dependencies: - "@babel/types" "^7.24.7" - "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" @@ -255,14 +204,6 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz#37d66feb012024f2422b762b9b2a7cfe27c7fba3" - integrity sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - "@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" @@ -274,17 +215,6 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-compilation-targets@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz#4eb6c4a80d6ffeac25ab8cd9a21b5dfa48d503a9" - integrity sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg== - dependencies: - "@babel/compat-data" "^7.24.7" - "@babel/helper-validator-option" "^7.24.7" - browserslist "^4.22.2" - lru-cache "^5.1.1" - semver "^6.3.1" - "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.6": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.0.tgz#fc7554141bdbfa2d17f7b4b80153b9b090e5d158" @@ -300,21 +230,6 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz#2eaed36b3a1c11c53bdf80d53838b293c52f5b3b" - integrity sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-member-expression-to-functions" "^7.24.7" - "@babel/helper-optimise-call-expression" "^7.24.7" - "@babel/helper-replace-supers" "^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" - semver "^6.3.1" - "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" @@ -324,15 +239,6 @@ regexpu-core "^5.3.1" semver "^6.3.1" -"@babel/helper-create-regexp-features-plugin@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz#be4f435a80dc2b053c76eeb4b7d16dd22cfc89da" - integrity sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - regexpu-core "^5.3.1" - semver "^6.3.1" - "@babel/helper-define-polyfill-provider@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.5.0.tgz#465805b7361f461e86c680f1de21eaf88c25901b" @@ -344,29 +250,11 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" - integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== - dependencies: - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-plugin-utils" "^7.22.5" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - "@babel/helper-environment-visitor@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== -"@babel/helper-environment-visitor@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" - integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== - dependencies: - "@babel/types" "^7.24.7" - "@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" @@ -375,14 +263,6 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.23.0" -"@babel/helper-function-name@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" - integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== - dependencies: - "@babel/template" "^7.24.7" - "@babel/types" "^7.24.7" - "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" @@ -390,13 +270,6 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-hoist-variables@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" - integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== - dependencies: - "@babel/types" "^7.24.7" - "@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" @@ -404,14 +277,6 @@ dependencies: "@babel/types" "^7.23.0" -"@babel/helper-member-expression-to-functions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz#67613d068615a70e4ed5101099affc7a41c5225f" - integrity sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - "@babel/helper-module-imports@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" @@ -419,14 +284,6 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-module-imports@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" - integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - "@babel/helper-module-transforms@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" @@ -438,17 +295,6 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" -"@babel/helper-module-transforms@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz#31b6c9a2930679498db65b685b1698bfd6c7daf8" - integrity sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ== - dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-simple-access" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" - "@babel/helper-validator-identifier" "^7.24.7" - "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" @@ -456,23 +302,11 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-optimise-call-expression@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" - integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A== - dependencies: - "@babel/types" "^7.24.7" - "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== -"@babel/helper-plugin-utils@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz#98c84fe6fe3d0d3ae7bfc3a5e166a46844feb2a0" - integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== - "@babel/helper-remap-async-to-generator@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" @@ -482,15 +316,6 @@ "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-wrap-function" "^7.22.20" -"@babel/helper-remap-async-to-generator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz#b3f0f203628522713849d49403f1a414468be4c7" - integrity sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-wrap-function" "^7.24.7" - "@babel/helper-replace-supers@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" @@ -500,15 +325,6 @@ "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" -"@babel/helper-replace-supers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz#f933b7eed81a1c0265740edc91491ce51250f765" - integrity sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg== - dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-member-expression-to-functions" "^7.24.7" - "@babel/helper-optimise-call-expression" "^7.24.7" - "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" @@ -516,14 +332,6 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-simple-access@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" - integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" @@ -531,14 +339,6 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-skip-transparent-expression-wrappers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" - integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" @@ -546,43 +346,21 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-split-export-declaration@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" - integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== - dependencies: - "@babel/types" "^7.24.7" - "@babel/helper-string-parser@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== -"@babel/helper-string-parser@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" - integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== - "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== -"@babel/helper-validator-identifier@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" - integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== - "@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== -"@babel/helper-validator-option@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" - integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== - "@babel/helper-wrap-function@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569" @@ -592,16 +370,6 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.19" -"@babel/helper-wrap-function@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz#52d893af7e42edca7c6d2c6764549826336aae1f" - integrity sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw== - dependencies: - "@babel/helper-function-name" "^7.24.7" - "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - "@babel/helpers@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.0.tgz#a3dd462b41769c95db8091e49cfe019389a9409b" @@ -611,14 +379,6 @@ "@babel/traverse" "^7.24.0" "@babel/types" "^7.24.0" -"@babel/helpers@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.7.tgz#aa2ccda29f62185acb5d42fb4a3a1b1082107416" - integrity sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg== - dependencies: - "@babel/template" "^7.24.7" - "@babel/types" "^7.24.7" - "@babel/highlight@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" @@ -628,34 +388,11 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/highlight@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" - integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.7" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - "@babel/parser@^7.22.7", "@babel/parser@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz#26a3d1ff49031c53a97d03b604375f028746a9ac" integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg== -"@babel/parser@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" - integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== - -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz#fd059fd27b184ea2b4c7e646868a9a381bbc3055" - integrity sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ== - dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a" @@ -663,13 +400,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz#468096ca44bbcbe8fcc570574e12eb1950e18107" - integrity sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz#f6652bb16b94f8f9c20c50941e16e9756898dc5d" @@ -679,15 +409,6 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-optional-chaining" "^7.23.3" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz#e4eabdd5109acc399b38d7999b2ef66fc2022f89" - integrity sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/plugin-transform-optional-chaining" "^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.7": version "7.23.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz#516462a95d10a9618f197d39ad291a9b47ae1d7b" @@ -696,14 +417,6 @@ "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz#71b21bb0286d5810e63a1538aa901c58e87375ec" - integrity sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg== - dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" @@ -751,13 +464,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-syntax-import-assertions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz#2a0b406b5871a20a841240586b1300ce2088a778" - integrity sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-import-attributes@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz#992aee922cf04512461d7dae3ff6951b90a2dc06" @@ -765,13 +471,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-syntax-import-attributes@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz#b4f9ea95a79e6912480c4b626739f86a076624ca" - integrity sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -793,13 +492,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-syntax-jsx@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" - integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -863,13 +555,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-syntax-typescript@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" - integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" @@ -885,13 +570,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-arrow-functions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz#4f6886c11e423bd69f3ce51dbf42424a5f275514" - integrity sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-async-generator-functions@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz#9adaeb66fc9634a586c5df139c6240d41ed801ce" @@ -902,16 +580,6 @@ "@babel/helper-remap-async-to-generator" "^7.22.20" "@babel/plugin-syntax-async-generators" "^7.8.4" -"@babel/plugin-transform-async-generator-functions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz#7330a5c50e05181ca52351b8fd01642000c96cfd" - integrity sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g== - dependencies: - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-remap-async-to-generator" "^7.24.7" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-transform-async-to-generator@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz#d1f513c7a8a506d43f47df2bf25f9254b0b051fa" @@ -921,15 +589,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-remap-async-to-generator" "^7.22.20" -"@babel/plugin-transform-async-to-generator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz#72a3af6c451d575842a7e9b5a02863414355bdcc" - integrity sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA== - dependencies: - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-remap-async-to-generator" "^7.24.7" - "@babel/plugin-transform-block-scoped-functions@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz#fe1177d715fb569663095e04f3598525d98e8c77" @@ -937,13 +596,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-block-scoped-functions@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz#a4251d98ea0c0f399dafe1a35801eaba455bbf1f" - integrity sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-block-scoping@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz#b2d38589531c6c80fbe25e6b58e763622d2d3cf5" @@ -951,13 +603,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-block-scoping@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz#42063e4deb850c7bd7c55e626bf4e7ab48e6ce02" - integrity sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-class-properties@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz#35c377db11ca92a785a718b6aa4e3ed1eb65dc48" @@ -966,14 +611,6 @@ "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-class-properties@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz#256879467b57b0b68c7ddfc5b76584f398cd6834" - integrity sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-class-static-block@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz#2a202c8787a8964dd11dfcedf994d36bfc844ab5" @@ -983,15 +620,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-class-static-block@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz#c82027ebb7010bc33c116d4b5044fbbf8c05484d" - integrity sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-transform-classes@^7.23.8": version "7.23.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz#d08ae096c240347badd68cdf1b6d1624a6435d92" @@ -1006,20 +634,6 @@ "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" -"@babel/plugin-transform-classes@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz#4ae6ef43a12492134138c1e45913f7c46c41b4bf" - integrity sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-replace-supers" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" - globals "^11.1.0" - "@babel/plugin-transform-computed-properties@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz#652e69561fcc9d2b50ba4f7ac7f60dcf65e86474" @@ -1028,14 +642,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/template" "^7.22.15" -"@babel/plugin-transform-computed-properties@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz#4cab3214e80bc71fae3853238d13d097b004c707" - integrity sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/template" "^7.24.7" - "@babel/plugin-transform-destructuring@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz#8c9ee68228b12ae3dff986e56ed1ba4f3c446311" @@ -1043,13 +649,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-destructuring@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz#a097f25292defb6e6cc16d6333a4cfc1e3c72d9e" - integrity sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-dotall-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz#3f7af6054882ede89c378d0cf889b854a993da50" @@ -1058,14 +657,6 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-dotall-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz#5f8bf8a680f2116a7207e16288a5f974ad47a7a0" - integrity sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-duplicate-keys@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz#664706ca0a5dfe8d066537f99032fc1dc8b720ce" @@ -1073,13 +664,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-duplicate-keys@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz#dd20102897c9a2324e5adfffb67ff3610359a8ee" - integrity sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-dynamic-import@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz#c7629e7254011ac3630d47d7f34ddd40ca535143" @@ -1088,14 +672,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-transform-dynamic-import@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz#4d8b95e3bae2b037673091aa09cd33fecd6419f4" - integrity sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-exponentiation-operator@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz#ea0d978f6b9232ba4722f3dbecdd18f450babd18" @@ -1104,14 +680,6 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-exponentiation-operator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz#b629ee22645f412024297d5245bce425c31f9b0d" - integrity sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ== - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-export-namespace-from@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz#084c7b25e9a5c8271e987a08cf85807b80283191" @@ -1120,14 +688,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" -"@babel/plugin-transform-export-namespace-from@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz#176d52d8d8ed516aeae7013ee9556d540c53f197" - integrity sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-transform-for-of@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e" @@ -1136,14 +696,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" -"@babel/plugin-transform-for-of@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz#f25b33f72df1d8be76399e1b8f3f9d366eb5bc70" - integrity sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/plugin-transform-function-name@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz#8f424fcd862bf84cb9a1a6b42bc2f47ed630f8dc" @@ -1153,15 +705,6 @@ "@babel/helper-function-name" "^7.23.0" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-function-name@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz#6d8601fbffe665c894440ab4470bc721dd9131d6" - integrity sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w== - dependencies: - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-json-strings@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz#a871d9b6bd171976efad2e43e694c961ffa3714d" @@ -1170,14 +713,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-transform-json-strings@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz#f3e9c37c0a373fee86e36880d45b3664cedaf73a" - integrity sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-transform-literals@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz#8214665f00506ead73de157eba233e7381f3beb4" @@ -1185,13 +720,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-literals@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz#36b505c1e655151a9d7607799a9988fc5467d06c" - integrity sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-logical-assignment-operators@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz#e599f82c51d55fac725f62ce55d3a0886279ecb5" @@ -1200,14 +728,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" -"@babel/plugin-transform-logical-assignment-operators@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz#a58fb6eda16c9dc8f9ff1c7b1ba6deb7f4694cb0" - integrity sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-transform-member-expression-literals@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz#e37b3f0502289f477ac0e776b05a833d853cabcc" @@ -1215,13 +735,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-member-expression-literals@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz#3b4454fb0e302e18ba4945ba3246acb1248315df" - integrity sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-modules-amd@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz#e19b55436a1416829df0a1afc495deedfae17f7d" @@ -1230,14 +743,6 @@ "@babel/helper-module-transforms" "^7.23.3" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-modules-amd@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz#65090ed493c4a834976a3ca1cde776e6ccff32d7" - integrity sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg== - dependencies: - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-modules-commonjs@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz#661ae831b9577e52be57dd8356b734f9700b53b4" @@ -1247,15 +752,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" -"@babel/plugin-transform-modules-commonjs@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz#9fd5f7fdadee9085886b183f1ad13d1ab260f4ab" - integrity sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ== - dependencies: - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-simple-access" "^7.24.7" - "@babel/plugin-transform-modules-systemjs@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.9.tgz#105d3ed46e4a21d257f83a2f9e2ee4203ceda6be" @@ -1266,16 +762,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.20" -"@babel/plugin-transform-modules-systemjs@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz#f8012316c5098f6e8dee6ecd58e2bc6f003d0ce7" - integrity sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw== - dependencies: - "@babel/helper-hoist-variables" "^7.24.7" - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-validator-identifier" "^7.24.7" - "@babel/plugin-transform-modules-umd@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz#5d4395fccd071dfefe6585a4411aa7d6b7d769e9" @@ -1284,14 +770,6 @@ "@babel/helper-module-transforms" "^7.23.3" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-modules-umd@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz#edd9f43ec549099620df7df24e7ba13b5c76efc8" - integrity sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A== - dependencies: - "@babel/helper-module-transforms" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" @@ -1300,14 +778,6 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz#9042e9b856bc6b3688c0c2e4060e9e10b1460923" - integrity sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-new-target@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz#5491bb78ed6ac87e990957cea367eab781c4d980" @@ -1315,13 +785,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-new-target@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz#31ff54c4e0555cc549d5816e4ab39241dfb6ab00" - integrity sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz#45556aad123fc6e52189ea749e33ce090637346e" @@ -1330,14 +793,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" -"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz#1de4534c590af9596f53d67f52a92f12db984120" - integrity sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-transform-numeric-separator@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz#03d08e3691e405804ecdd19dd278a40cca531f29" @@ -1346,14 +801,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-transform-numeric-separator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz#bea62b538c80605d8a0fac9b40f48e97efa7de63" - integrity sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-transform-object-rest-spread@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.0.tgz#7b836ad0088fdded2420ce96d4e1d3ed78b71df1" @@ -1365,16 +812,6 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.23.3" -"@babel/plugin-transform-object-rest-spread@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz#d13a2b93435aeb8a197e115221cab266ba6e55d6" - integrity sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q== - dependencies: - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.24.7" - "@babel/plugin-transform-object-super@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz#81fdb636dcb306dd2e4e8fd80db5b2362ed2ebcd" @@ -1383,14 +820,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.20" -"@babel/plugin-transform-object-super@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz#66eeaff7830bba945dd8989b632a40c04ed625be" - integrity sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-replace-supers" "^7.24.7" - "@babel/plugin-transform-optional-catch-binding@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz#318066de6dacce7d92fa244ae475aa8d91778017" @@ -1399,14 +828,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-transform-optional-catch-binding@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz#00eabd883d0dd6a60c1c557548785919b6e717b4" - integrity sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-transform-optional-chaining@^7.23.3", "@babel/plugin-transform-optional-chaining@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz#6acf61203bdfc4de9d4e52e64490aeb3e52bd017" @@ -1416,15 +837,6 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -"@babel/plugin-transform-optional-chaining@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz#b8f6848a80cf2da98a8a204429bec04756c6d454" - integrity sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-transform-parameters@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz#83ef5d1baf4b1072fa6e54b2b0999a7b2527e2af" @@ -1432,13 +844,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-parameters@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz#5881f0ae21018400e320fc7eb817e529d1254b68" - integrity sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-private-methods@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz#b2d7a3c97e278bfe59137a978d53b2c2e038c0e4" @@ -1447,14 +852,6 @@ "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-private-methods@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz#e6318746b2ae70a59d023d5cc1344a2ba7a75f5e" - integrity sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-private-property-in-object@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz#3ec711d05d6608fd173d9b8de39872d8dbf68bf5" @@ -1465,16 +862,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" -"@babel/plugin-transform-private-property-in-object@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz#4eec6bc701288c1fab5f72e6a4bbc9d67faca061" - integrity sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-transform-property-literals@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz#54518f14ac4755d22b92162e4a852d308a560875" @@ -1482,13 +869,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-property-literals@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz#f0d2ed8380dfbed949c42d4d790266525d63bbdc" - integrity sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-react-constant-elements@^7.18.12": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.23.3.tgz#5efc001d07ef0f7da0d73c3a86c132f73d28e43c" @@ -1496,13 +876,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-react-constant-elements@^7.21.3": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.7.tgz#b85e8f240b14400277f106c9c9b585d9acf608a1" - integrity sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-react-display-name@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz#70529f034dd1e561045ad3c8152a267f0d7b6200" @@ -1544,14 +917,6 @@ "@babel/helper-plugin-utils" "^7.22.5" regenerator-transform "^0.15.2" -"@babel/plugin-transform-regenerator@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz#021562de4534d8b4b1851759fd7af4e05d2c47f8" - integrity sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - regenerator-transform "^0.15.2" - "@babel/plugin-transform-reserved-words@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz#4130dcee12bd3dd5705c587947eb715da12efac8" @@ -1559,13 +924,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-reserved-words@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz#80037fe4fbf031fc1125022178ff3938bb3743a4" - integrity sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-runtime@^7.22.9": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz#e308fe27d08b74027d42547081eefaf4f2ffbcc9" @@ -1585,13 +943,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-shorthand-properties@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz#85448c6b996e122fa9e289746140aaa99da64e73" - integrity sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-spread@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz#41d17aacb12bde55168403c6f2d6bdca563d362c" @@ -1600,14 +951,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" -"@babel/plugin-transform-spread@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz#e8a38c0fde7882e0fb8f160378f74bd885cc7bb3" - integrity sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/plugin-transform-sticky-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz#dec45588ab4a723cb579c609b294a3d1bd22ff04" @@ -1615,13 +958,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-sticky-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz#96ae80d7a7e5251f657b5cf18f1ea6bf926f5feb" - integrity sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-template-literals@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz#5f0f028eb14e50b5d0f76be57f90045757539d07" @@ -1629,13 +965,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-template-literals@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz#a05debb4a9072ae8f985bcf77f3f215434c8f8c8" - integrity sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-typeof-symbol@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz#9dfab97acc87495c0c449014eb9c547d8966bca4" @@ -1643,13 +972,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-typeof-symbol@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz#f074be466580d47d6e6b27473a840c9f9ca08fb0" - integrity sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-typescript@^7.23.3": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz#aa36a94e5da8d94339ae3a4e22d40ed287feb34c" @@ -1660,16 +982,6 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.23.3" -"@babel/plugin-transform-typescript@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.7.tgz#b006b3e0094bf0813d505e0c5485679eeaf4a881" - integrity sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.24.7" - "@babel/helper-create-class-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-syntax-typescript" "^7.24.7" - "@babel/plugin-transform-unicode-escapes@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz#1f66d16cab01fab98d784867d24f70c1ca65b925" @@ -1677,13 +989,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-escapes@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz#2023a82ced1fb4971630a2e079764502c4148e0e" - integrity sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-unicode-property-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz#19e234129e5ffa7205010feec0d94c251083d7ad" @@ -1692,14 +997,6 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-property-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz#9073a4cd13b86ea71c3264659590ac086605bbcd" - integrity sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-unicode-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz#26897708d8f42654ca4ce1b73e96140fbad879dc" @@ -1708,14 +1005,6 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz#dfc3d4a51127108099b19817c0963be6a2adf19f" - integrity sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/plugin-transform-unicode-sets-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz#4fb6f0a719c2c5859d11f6b55a050cc987f3799e" @@ -1724,14 +1013,6 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-unicode-sets-regex@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz#d40705d67523803a576e29c63cef6e516b858ed9" - integrity sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/preset-env@^7.19.4", "@babel/preset-env@^7.22.9": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.0.tgz#11536a7f4b977294f0bdfad780f01a8ac8e183fc" @@ -1818,93 +1099,6 @@ core-js-compat "^3.31.0" semver "^6.3.1" -"@babel/preset-env@^7.20.2": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.7.tgz#ff067b4e30ba4a72f225f12f123173e77b987f37" - integrity sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ== - dependencies: - "@babel/compat-data" "^7.24.7" - "@babel/helper-compilation-targets" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-validator-option" "^7.24.7" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.7" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.7" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.7" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.7" - "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-import-assertions" "^7.24.7" - "@babel/plugin-syntax-import-attributes" "^7.24.7" - "@babel/plugin-syntax-import-meta" "^7.10.4" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.24.7" - "@babel/plugin-transform-async-generator-functions" "^7.24.7" - "@babel/plugin-transform-async-to-generator" "^7.24.7" - "@babel/plugin-transform-block-scoped-functions" "^7.24.7" - "@babel/plugin-transform-block-scoping" "^7.24.7" - "@babel/plugin-transform-class-properties" "^7.24.7" - "@babel/plugin-transform-class-static-block" "^7.24.7" - "@babel/plugin-transform-classes" "^7.24.7" - "@babel/plugin-transform-computed-properties" "^7.24.7" - "@babel/plugin-transform-destructuring" "^7.24.7" - "@babel/plugin-transform-dotall-regex" "^7.24.7" - "@babel/plugin-transform-duplicate-keys" "^7.24.7" - "@babel/plugin-transform-dynamic-import" "^7.24.7" - "@babel/plugin-transform-exponentiation-operator" "^7.24.7" - "@babel/plugin-transform-export-namespace-from" "^7.24.7" - "@babel/plugin-transform-for-of" "^7.24.7" - "@babel/plugin-transform-function-name" "^7.24.7" - "@babel/plugin-transform-json-strings" "^7.24.7" - "@babel/plugin-transform-literals" "^7.24.7" - "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" - "@babel/plugin-transform-member-expression-literals" "^7.24.7" - "@babel/plugin-transform-modules-amd" "^7.24.7" - "@babel/plugin-transform-modules-commonjs" "^7.24.7" - "@babel/plugin-transform-modules-systemjs" "^7.24.7" - "@babel/plugin-transform-modules-umd" "^7.24.7" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" - "@babel/plugin-transform-new-target" "^7.24.7" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" - "@babel/plugin-transform-numeric-separator" "^7.24.7" - "@babel/plugin-transform-object-rest-spread" "^7.24.7" - "@babel/plugin-transform-object-super" "^7.24.7" - "@babel/plugin-transform-optional-catch-binding" "^7.24.7" - "@babel/plugin-transform-optional-chaining" "^7.24.7" - "@babel/plugin-transform-parameters" "^7.24.7" - "@babel/plugin-transform-private-methods" "^7.24.7" - "@babel/plugin-transform-private-property-in-object" "^7.24.7" - "@babel/plugin-transform-property-literals" "^7.24.7" - "@babel/plugin-transform-regenerator" "^7.24.7" - "@babel/plugin-transform-reserved-words" "^7.24.7" - "@babel/plugin-transform-shorthand-properties" "^7.24.7" - "@babel/plugin-transform-spread" "^7.24.7" - "@babel/plugin-transform-sticky-regex" "^7.24.7" - "@babel/plugin-transform-template-literals" "^7.24.7" - "@babel/plugin-transform-typeof-symbol" "^7.24.7" - "@babel/plugin-transform-unicode-escapes" "^7.24.7" - "@babel/plugin-transform-unicode-property-regex" "^7.24.7" - "@babel/plugin-transform-unicode-regex" "^7.24.7" - "@babel/plugin-transform-unicode-sets-regex" "^7.24.7" - "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.4" - babel-plugin-polyfill-regenerator "^0.6.1" - core-js-compat "^3.31.0" - semver "^6.3.1" - "@babel/preset-modules@0.1.6-no-external-plugins": version "0.1.6-no-external-plugins" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" @@ -1937,17 +1131,6 @@ "@babel/plugin-transform-modules-commonjs" "^7.23.3" "@babel/plugin-transform-typescript" "^7.23.3" -"@babel/preset-typescript@^7.21.0": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz#66cd86ea8f8c014855671d5ea9a737139cbbfef1" - integrity sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.7" - "@babel/helper-validator-option" "^7.24.7" - "@babel/plugin-syntax-jsx" "^7.24.7" - "@babel/plugin-transform-modules-commonjs" "^7.24.7" - "@babel/plugin-transform-typescript" "^7.24.7" - "@babel/regjsgen@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" @@ -1977,15 +1160,6 @@ "@babel/parser" "^7.24.0" "@babel/types" "^7.24.0" -"@babel/template@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" - integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== - dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/types" "^7.24.7" - "@babel/traverse@^7.22.8", "@babel/traverse@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.0.tgz#4a408fbf364ff73135c714a2ab46a5eab2831b1e" @@ -2002,22 +1176,6 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/traverse@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" - integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== - dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.7" - "@babel/helper-environment-visitor" "^7.24.7" - "@babel/helper-function-name" "^7.24.7" - "@babel/helper-hoist-variables" "^7.24.7" - "@babel/helper-split-export-declaration" "^7.24.7" - "@babel/parser" "^7.24.7" - "@babel/types" "^7.24.7" - debug "^4.3.1" - globals "^11.1.0" - "@babel/types@^7.20.0", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.6", "@babel/types@^7.24.0", "@babel/types@^7.4.4": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" @@ -2027,15 +1185,6 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@babel/types@^7.21.3", "@babel/types@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" - integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== - dependencies: - "@babel/helper-string-parser" "^7.24.7" - "@babel/helper-validator-identifier" "^7.24.7" - to-fast-properties "^2.0.0" - "@braintree/sanitize-url@^6.0.1": version "6.0.4" resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" @@ -2141,10 +1290,10 @@ webpack-merge "^5.9.0" webpackbar "^5.0.2" -"@docusaurus/core@3.4.0", "@docusaurus/core@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.4.0.tgz#bdbf1af4b2f25d1bf4a5b62ec6137d84c821cb3c" - integrity sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w== +"@docusaurus/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-3.2.0.tgz#10acb993fb76960890d1aa43025245aaa8dcdbbb" + integrity sha512-WTO6vW4404nhTmK9NL+95nd13I1JveFwZ8iOBYxb4xt+N2S3KzY+mm+1YtWw2vV37FbYfH+w+KrlrRaWuy5Hzw== dependencies: "@babel/core" "^7.23.3" "@babel/generator" "^7.23.3" @@ -2156,12 +1305,14 @@ "@babel/runtime" "^7.22.6" "@babel/runtime-corejs3" "^7.22.6" "@babel/traverse" "^7.22.8" - "@docusaurus/cssnano-preset" "3.4.0" - "@docusaurus/logger" "3.4.0" - "@docusaurus/mdx-loader" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-common" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" + "@docusaurus/cssnano-preset" "3.2.0" + "@docusaurus/logger" "3.2.0" + "@docusaurus/mdx-loader" "3.2.0" + "@docusaurus/react-loadable" "5.5.2" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-common" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" + "@svgr/webpack" "^6.5.1" autoprefixer "^10.4.14" babel-loader "^9.1.3" babel-plugin-dynamic-import-node "^2.3.3" @@ -2175,8 +1326,8 @@ copy-webpack-plugin "^11.0.0" core-js "^3.31.1" css-loader "^6.8.1" - css-minimizer-webpack-plugin "^5.0.1" - cssnano "^6.1.2" + css-minimizer-webpack-plugin "^4.2.2" + cssnano "^5.1.15" del "^6.1.1" detect-port "^1.5.1" escape-html "^1.0.3" @@ -2196,7 +1347,7 @@ prompts "^2.4.2" react-dev-utils "^12.0.1" react-helmet-async "^1.3.0" - react-loadable "npm:@docusaurus/react-loadable@6.0.0" + react-loadable "npm:@docusaurus/react-loadable@5.5.2" react-loadable-ssr-addon-v5-slorber "^1.0.1" react-router "^5.3.4" react-router-config "^5.1.1" @@ -2225,14 +1376,14 @@ postcss-sort-media-queries "^4.4.1" tslib "^2.6.0" -"@docusaurus/cssnano-preset@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz#dc7922b3bbeabcefc9b60d0161680d81cf72c368" - integrity sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ== +"@docusaurus/cssnano-preset@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-3.2.0.tgz#0e0fbf19873a726f92e670b9d511e9f2828d6097" + integrity sha512-H88RXGUia7r/VF3XfyoA4kbwgpUZcKsObF6VvwBOP91EdArTf6lnHbJ/x8Ca79KS/zf98qaWyBGzW+5ez58Iyw== dependencies: - cssnano-preset-advanced "^6.1.2" - postcss "^8.4.38" - postcss-sort-media-queries "^5.2.0" + cssnano-preset-advanced "^5.3.10" + postcss "^8.4.26" + postcss-sort-media-queries "^4.4.1" tslib "^2.6.0" "@docusaurus/logger@3.1.1": @@ -2243,10 +1394,10 @@ chalk "^4.1.2" tslib "^2.6.0" -"@docusaurus/logger@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.4.0.tgz#8b0ac05c7f3dac2009066e2f964dee8209a77403" - integrity sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q== +"@docusaurus/logger@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-3.2.0.tgz#99d2b09478bcba69c964ec0c8600d855fb8e9e0f" + integrity sha512-Z1R1NcOGXZ8CkIJSvjvyxnuDDSlx/+1xlh20iVTw1DZRjonFmI3T3tTgk40YpXyWUYQpIgAoqqPMpuseMMdgRQ== dependencies: chalk "^4.1.2" tslib "^2.6.0" @@ -2283,14 +1434,14 @@ vfile "^6.0.1" webpack "^5.88.1" -"@docusaurus/mdx-loader@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz#483d7ab57928fdbb5c8bd1678098721a930fc5f6" - integrity sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw== +"@docusaurus/mdx-loader@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-3.2.0.tgz#d17f17ae1bb38255643c82705dda719b23c27831" + integrity sha512-JtkI5o6R/rJSr1Y23cHKz085aBJCvJw3AYHihJ7r+mBX+O8EuQIynG0e6/XpbSCpr7Ino0U50UtxaXcEbFwg9Q== dependencies: - "@docusaurus/logger" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" + "@docusaurus/logger" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" "@mdx-js/mdx" "^3.0.0" "@slorber/remark-comment" "^1.0.0" escape-html "^1.0.3" @@ -2327,46 +1478,47 @@ react-helmet-async "*" react-loadable "npm:@docusaurus/react-loadable@5.5.2" -"@docusaurus/module-type-aliases@3.4.0", "@docusaurus/module-type-aliases@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz#2653bde58fc1aa3dbc626a6c08cfb63a37ae1bb8" - integrity sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw== +"@docusaurus/module-type-aliases@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-3.2.0.tgz#ef883d8418f37e551eca72adc409014e720786d4" + integrity sha512-jRSp9YkvBwwNz6Xgy0RJPsnie+Ebb//gy7GdbkJ2pW2gvvlYKGib2+jSF0pfIzvyZLulfCynS1KQdvDKdSl8zQ== dependencies: - "@docusaurus/types" "3.4.0" + "@docusaurus/react-loadable" "5.5.2" + "@docusaurus/types" "3.2.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" "@types/react-router-dom" "*" react-helmet-async "*" - react-loadable "npm:@docusaurus/react-loadable@6.0.0" - -"@docusaurus/plugin-client-redirects@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.4.0.tgz#10eafc9adcf3f9be7cc33d77e816040dc7a8d368" - integrity sha512-Pr8kyh/+OsmYCvdZhc60jy/FnrY6flD2TEAhl4rJxeVFxnvvRgEhoaIVX8q9MuJmaQoh6frPk94pjs7/6YgBDQ== - dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/logger" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-common" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" + react-loadable "npm:@docusaurus/react-loadable@5.5.2" + +"@docusaurus/plugin-client-redirects@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.2.0.tgz#4c98c185620ce10830e1cc8a7f16a807857cb654" + integrity sha512-re5bgvYOgBHmevlI8HO3fZHL7mvX2lAULr4E89n/bQ5kgekLLhsaerWrAah22ZluMZyJC2439EGjR63E9Ba6KA== + dependencies: + "@docusaurus/core" "3.2.0" + "@docusaurus/logger" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-common" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" eta "^2.2.0" fs-extra "^11.1.1" lodash "^4.17.21" tslib "^2.6.0" -"@docusaurus/plugin-content-blog@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz#6373632fdbababbda73a13c4a08f907d7de8f007" - integrity sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw== - dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/logger" "3.4.0" - "@docusaurus/mdx-loader" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-common" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" +"@docusaurus/plugin-content-blog@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.2.0.tgz#b7b43e71634272a80a9532dc166731332391cb4b" + integrity sha512-MABqwjSicyHmYEfQueMthPCz18JkVxhK3EGhXTSRWwReAZ0UTuw9pG6+Wo+uXAugDaIcJH28rVZSwTDINPm2bw== + dependencies: + "@docusaurus/core" "3.2.0" + "@docusaurus/logger" "3.2.0" + "@docusaurus/mdx-loader" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-common" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" cheerio "^1.0.0-rc.12" feed "^4.2.2" fs-extra "^11.1.1" @@ -2378,19 +1530,19 @@ utility-types "^3.10.0" webpack "^5.88.1" -"@docusaurus/plugin-content-docs@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz#3088973f72169a2a6d533afccec7153c8720d332" - integrity sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg== - dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/logger" "3.4.0" - "@docusaurus/mdx-loader" "3.4.0" - "@docusaurus/module-type-aliases" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-common" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" +"@docusaurus/plugin-content-docs@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.2.0.tgz#6e4f727a0cce301b9d9361bf41ca6a978fe79475" + integrity sha512-uuqhahmsBnirxOz+SXksnWt7+wc+iN4ntxNRH48BUgo7QRNLATWjHCgI8t6zrMJxK4o+QL9DhLaPDlFHs91B3Q== + dependencies: + "@docusaurus/core" "3.2.0" + "@docusaurus/logger" "3.2.0" + "@docusaurus/mdx-loader" "3.2.0" + "@docusaurus/module-type-aliases" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-common" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" "@types/react-router-config" "^5.0.7" combine-promises "^1.1.0" fs-extra "^11.1.1" @@ -2421,96 +1573,96 @@ utility-types "^3.10.0" webpack "^5.88.1" -"@docusaurus/plugin-content-pages@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz#1846172ca0355c7d32a67ef8377750ce02bbb8ad" - integrity sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg== - dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/mdx-loader" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" +"@docusaurus/plugin-content-pages@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.2.0.tgz#df28a6ee6b52c4b292a726f29f39b119756caf44" + integrity sha512-4ofAN7JDsdb4tODO9OIrizWY5DmEJXr0eu+UDIkLqGP+gXXTahJZv8h2mlxO+lPXGXRCVBOfA14OG1hOYJVPwA== + dependencies: + "@docusaurus/core" "3.2.0" + "@docusaurus/mdx-loader" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" fs-extra "^11.1.1" tslib "^2.6.0" webpack "^5.88.1" -"@docusaurus/plugin-debug@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz#74e4ec5686fa314c26f3ac150bacadbba7f06948" - integrity sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg== +"@docusaurus/plugin-debug@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-3.2.0.tgz#643d13d403685c2b9bdb3b65bec8050847b920a3" + integrity sha512-p6WxtO5XZGz66y6QNQtCJwBefq4S6/w75XaXVvH1/2P9uaijvF7R+Cm2EWQZ5WsvA5wl//DFWblyDHRyVC207Q== dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils" "3.4.0" + "@docusaurus/core" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils" "3.2.0" fs-extra "^11.1.1" react-json-view-lite "^1.2.0" tslib "^2.6.0" -"@docusaurus/plugin-google-analytics@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz#5f59fc25329a59decc231936f6f9fb5663da3c55" - integrity sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA== +"@docusaurus/plugin-google-analytics@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.2.0.tgz#770151947c0ee49500586e9200631852ab97e23a" + integrity sha512-//TepJTEyAZSvBwHKEbXHu9xT/VkK3wUil2ZakKvQZYfUC01uWn6A1E3toa8R7WhCy1xPUeIukqmJy1Clg8njQ== dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" + "@docusaurus/core" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" tslib "^2.6.0" -"@docusaurus/plugin-google-gtag@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz#42489ac5fe1c83b5523ceedd5ef74f9aa8bc251b" - integrity sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA== +"@docusaurus/plugin-google-gtag@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.2.0.tgz#65fc7ddc242185c3a10e60308471564075229406" + integrity sha512-3s6zxlaMMb87MW2Rxy6EnSRDs0WDEQPuHilZZH402C8kOrUnIwlhlfjWZ4ZyLDziGl/Eec/DvD0PVqj0qHRomA== dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" + "@docusaurus/core" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" "@types/gtag.js" "^0.0.12" tslib "^2.6.0" -"@docusaurus/plugin-google-tag-manager@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz#cebb03a5ffa1e70b37d95601442babea251329ff" - integrity sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ== +"@docusaurus/plugin-google-tag-manager@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.2.0.tgz#730c28a43ff5073f595509c6cb77ce4311a2e369" + integrity sha512-rAKtsJ11vPHA7dTAqWCgyIy7AyFRF/lpI77Zd/4HKgqcIvIayVBvL3QtelhUazfYTLTH6ls6kQ9wjMcIFxRiGg== dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" + "@docusaurus/core" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" tslib "^2.6.0" -"@docusaurus/plugin-sitemap@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz#b091d64d1e3c6c872050189999580187537bcbc6" - integrity sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q== - dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/logger" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-common" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" +"@docusaurus/plugin-sitemap@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.2.0.tgz#cae7c92a8631072fff39dd5caf5ea7608c795540" + integrity sha512-gnWDFt6MStjLkdtt63Lzc+14EPSd8B6mzJGJp9GQMvWDUoMAUijUqpVIHYQq+DPMcI4PJZ5I2nsl5XFf1vOldA== + dependencies: + "@docusaurus/core" "3.2.0" + "@docusaurus/logger" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-common" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" fs-extra "^11.1.1" sitemap "^7.1.1" tslib "^2.6.0" -"@docusaurus/preset-classic@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz#6082a32fbb465b0cb2c2a50ebfc277cff2c0f139" - integrity sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg== - dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/plugin-content-blog" "3.4.0" - "@docusaurus/plugin-content-docs" "3.4.0" - "@docusaurus/plugin-content-pages" "3.4.0" - "@docusaurus/plugin-debug" "3.4.0" - "@docusaurus/plugin-google-analytics" "3.4.0" - "@docusaurus/plugin-google-gtag" "3.4.0" - "@docusaurus/plugin-google-tag-manager" "3.4.0" - "@docusaurus/plugin-sitemap" "3.4.0" - "@docusaurus/theme-classic" "3.4.0" - "@docusaurus/theme-common" "3.4.0" - "@docusaurus/theme-search-algolia" "3.4.0" - "@docusaurus/types" "3.4.0" +"@docusaurus/preset-classic@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-3.2.0.tgz#f64d970eace76c61e4f1b4b7d85d9f69d4a2dd0e" + integrity sha512-t7tXyk8kUgT7hUqEOgSJnPs+Foem9ucuan/a9QVYaVFCDjp92Sb2FpCY8bVasAokYCjodYe2LfpAoSCj5YDYWg== + dependencies: + "@docusaurus/core" "3.2.0" + "@docusaurus/plugin-content-blog" "3.2.0" + "@docusaurus/plugin-content-docs" "3.2.0" + "@docusaurus/plugin-content-pages" "3.2.0" + "@docusaurus/plugin-debug" "3.2.0" + "@docusaurus/plugin-google-analytics" "3.2.0" + "@docusaurus/plugin-google-gtag" "3.2.0" + "@docusaurus/plugin-google-tag-manager" "3.2.0" + "@docusaurus/plugin-sitemap" "3.2.0" + "@docusaurus/theme-classic" "3.2.0" + "@docusaurus/theme-common" "3.2.0" + "@docusaurus/theme-search-algolia" "3.2.0" + "@docusaurus/types" "3.2.0" "@docusaurus/react-loadable@5.5.2": version "5.5.2" @@ -2520,23 +1672,23 @@ "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/theme-classic@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz#1b0f48edec3e3ec8927843554b9f11e5927b0e52" - integrity sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q== - dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/mdx-loader" "3.4.0" - "@docusaurus/module-type-aliases" "3.4.0" - "@docusaurus/plugin-content-blog" "3.4.0" - "@docusaurus/plugin-content-docs" "3.4.0" - "@docusaurus/plugin-content-pages" "3.4.0" - "@docusaurus/theme-common" "3.4.0" - "@docusaurus/theme-translations" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-common" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" +"@docusaurus/theme-classic@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-3.2.0.tgz#4aa229f1a4b1b4c138a5c80089f1d8146f56252c" + integrity sha512-4oSO5BQOJ5ja7WYdL6jK1n4J96tp+VJHamdwao6Ea252sA3W3vvR0otTflG4p4XVjNZH6hlPQoi5lKW0HeRgfQ== + dependencies: + "@docusaurus/core" "3.2.0" + "@docusaurus/mdx-loader" "3.2.0" + "@docusaurus/module-type-aliases" "3.2.0" + "@docusaurus/plugin-content-blog" "3.2.0" + "@docusaurus/plugin-content-docs" "3.2.0" + "@docusaurus/plugin-content-pages" "3.2.0" + "@docusaurus/theme-common" "3.2.0" + "@docusaurus/theme-translations" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-common" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" "@mdx-js/react" "^3.0.0" clsx "^2.0.0" copy-text-to-clipboard "^3.2.0" @@ -2551,18 +1703,18 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-common@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.4.0.tgz#01f2b728de6cb57f6443f52fc30675cf12a5d49f" - integrity sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA== - dependencies: - "@docusaurus/mdx-loader" "3.4.0" - "@docusaurus/module-type-aliases" "3.4.0" - "@docusaurus/plugin-content-blog" "3.4.0" - "@docusaurus/plugin-content-docs" "3.4.0" - "@docusaurus/plugin-content-pages" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-common" "3.4.0" +"@docusaurus/theme-common@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-3.2.0.tgz#67f5f1a1e265e1f1a5b9fa7bfb4bf7b98dfcf981" + integrity sha512-sFbw9XviNJJ+760kAcZCQMQ3jkNIznGqa6MQ70E5BnbP+ja36kGgPOfjcsvAcNey1H1Rkhh3p2Mhf4HVLdKVVw== + dependencies: + "@docusaurus/mdx-loader" "3.2.0" + "@docusaurus/module-type-aliases" "3.2.0" + "@docusaurus/plugin-content-blog" "3.2.0" + "@docusaurus/plugin-content-docs" "3.2.0" + "@docusaurus/plugin-content-pages" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-common" "3.2.0" "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" @@ -2572,32 +1724,32 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-mermaid@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.4.0.tgz#ef1d2231d0858767f67538b4fafd7d0ce2a3e845" - integrity sha512-3w5QW0HEZ2O6x2w6lU3ZvOe1gNXP2HIoKDMJBil1VmLBc9PmpAG17VmfhI/p3L2etNmOiVs5GgniUqvn8AFEGQ== - dependencies: - "@docusaurus/core" "3.4.0" - "@docusaurus/module-type-aliases" "3.4.0" - "@docusaurus/theme-common" "3.4.0" - "@docusaurus/types" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" +"@docusaurus/theme-mermaid@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-mermaid/-/theme-mermaid-3.2.0.tgz#b6d43b853ccc562a9c0be28e1100dad6324b8a8f" + integrity sha512-PvN6K6m3JaM9cr9oSPyba6OlwAiSfBzqQtNqdgPFDjakKuT4kj6JODfExi+HKtWuxayOVRQlRl7zTnWxM4sTVw== + dependencies: + "@docusaurus/core" "3.2.0" + "@docusaurus/module-type-aliases" "3.2.0" + "@docusaurus/theme-common" "3.2.0" + "@docusaurus/types" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" mermaid "^10.4.0" tslib "^2.6.0" -"@docusaurus/theme-search-algolia@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz#c499bad71d668df0d0f15b0e5e33e2fc4e330fcc" - integrity sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q== +"@docusaurus/theme-search-algolia@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.2.0.tgz#05338b37753dd13899fb0296f2c57130e9893dbf" + integrity sha512-PgvF4qHoqJp8+GfqClUbTF/zYNOsz4De251IuzXon7+7FAXwvb2qmYtA2nEwyMbB7faKOz33Pxzv+y+153KS/g== dependencies: "@docsearch/react" "^3.5.2" - "@docusaurus/core" "3.4.0" - "@docusaurus/logger" "3.4.0" - "@docusaurus/plugin-content-docs" "3.4.0" - "@docusaurus/theme-common" "3.4.0" - "@docusaurus/theme-translations" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-validation" "3.4.0" + "@docusaurus/core" "3.2.0" + "@docusaurus/logger" "3.2.0" + "@docusaurus/plugin-content-docs" "3.2.0" + "@docusaurus/theme-common" "3.2.0" + "@docusaurus/theme-translations" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-validation" "3.2.0" algoliasearch "^4.18.0" algoliasearch-helper "^3.13.3" clsx "^2.0.0" @@ -2607,10 +1759,10 @@ tslib "^2.6.0" utility-types "^3.10.0" -"@docusaurus/theme-translations@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz#e6355d01352886c67e38e848b2542582ea3070af" - integrity sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg== +"@docusaurus/theme-translations@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-3.2.0.tgz#02a0e9bd0ed8cebc21a2f1e5b6d252b0e5ee39a9" + integrity sha512-VXzZJBuyVEmwUYyud+7IgJQEBRM6R2u/s10Rp3DOP19CBQxeKgHYTKkKhFtDeKMHDassb665kjgOi0YlJfUT6w== dependencies: fs-extra "^11.1.1" tslib "^2.6.0" @@ -2638,10 +1790,10 @@ webpack "^5.88.1" webpack-merge "^5.9.0" -"@docusaurus/types@3.4.0", "@docusaurus/types@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.4.0.tgz#237c3f737e9db3f7c1a5935a3ef48d6eadde8292" - integrity sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A== +"@docusaurus/types@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.2.0.tgz#c5bfd000ad4f72e9a7e6beff79905f9ea165fcd3" + integrity sha512-uG3FfTkkkbZIPPNYx6xRfZHKeGyRd/inIT1cqvYt1FobFLd+7WhRXrSBqwJ9JajJjEAjNioRMVFgGofGf/Wdww== dependencies: "@mdx-js/mdx" "^3.0.0" "@types/history" "^4.7.11" @@ -2660,10 +1812,10 @@ dependencies: tslib "^2.6.0" -"@docusaurus/utils-common@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.4.0.tgz#2a43fefd35b85ab9fcc6833187e66c15f8bfbbc6" - integrity sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ== +"@docusaurus/utils-common@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-3.2.0.tgz#6163a2c415d150d6df73a8aceec6004f0ba3bb06" + integrity sha512-WEQT5L2lT/tBQgDRgeZQAIi9YJBrwEILb1BuObQn1St3T/4K1gx5fWwOT8qdLOov296XLd1FQg9Ywu27aE9svw== dependencies: tslib "^2.6.0" @@ -2678,18 +1830,16 @@ js-yaml "^4.1.0" tslib "^2.6.0" -"@docusaurus/utils-validation@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz#0176f6e503ff45f4390ec2ecb69550f55e0b5eb7" - integrity sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g== +"@docusaurus/utils-validation@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-3.2.0.tgz#b53463d9dc6eb335a2ad93ed4b3c397162533e6d" + integrity sha512-rCzMTqwNrBrEOyU8EaD1fYWdig4TDhfj+YLqB8DY68VUAqSIgbY+yshpqFKB0bznFYNBJbn0bGpvVuImQOa/vA== dependencies: - "@docusaurus/logger" "3.4.0" - "@docusaurus/utils" "3.4.0" - "@docusaurus/utils-common" "3.4.0" - fs-extra "^11.2.0" + "@docusaurus/logger" "3.2.0" + "@docusaurus/utils" "3.2.0" + "@docusaurus/utils-common" "3.2.0" joi "^17.9.2" js-yaml "^4.1.0" - lodash "^4.17.21" tslib "^2.6.0" "@docusaurus/utils@3.1.1", "@docusaurus/utils@^2 || ^3": @@ -2715,14 +1865,14 @@ url-loader "^4.1.1" webpack "^5.88.1" -"@docusaurus/utils@3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.4.0.tgz#c508e20627b7a55e2b541e4a28c95e0637d6a204" - integrity sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g== +"@docusaurus/utils@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-3.2.0.tgz#1312221d224eb2cbaaaf53d24efca3e1e976db3e" + integrity sha512-3rgrE7iL60yV2JQivlcoxUNNTK2APmn+OHLUmTvX2pueIM8DEOCEFHpJO4MiWjFO7V/Wq3iA/W1M03JnjdugVw== dependencies: - "@docusaurus/logger" "3.4.0" - "@docusaurus/utils-common" "3.4.0" - "@svgr/webpack" "^8.1.0" + "@docusaurus/logger" "3.2.0" + "@docusaurus/utils-common" "3.2.0" + "@svgr/webpack" "^6.5.1" escape-string-regexp "^4.0.0" file-loader "^6.2.0" fs-extra "^11.1.1" @@ -2738,7 +1888,6 @@ shelljs "^0.8.5" tslib "^2.6.0" url-loader "^4.1.1" - utility-types "^3.10.0" webpack "^5.88.1" "@easyops-cn/autocomplete.js@^0.38.1": @@ -2848,7 +1997,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.9": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -3095,90 +2244,46 @@ p-map "^4.0.0" webpack-sources "^3.2.2" -"@svgr/babel-plugin-add-jsx-attribute@8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" - integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== - "@svgr/babel-plugin-add-jsx-attribute@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba" integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== -"@svgr/babel-plugin-remove-jsx-attribute@*", "@svgr/babel-plugin-remove-jsx-attribute@8.0.0": +"@svgr/babel-plugin-remove-jsx-attribute@*": version "8.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== -"@svgr/babel-plugin-remove-jsx-empty-expression@*", "@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": +"@svgr/babel-plugin-remove-jsx-empty-expression@*": version "8.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== -"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" - integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== - "@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60" integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== -"@svgr/babel-plugin-svg-dynamic-title@8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" - integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== - "@svgr/babel-plugin-svg-dynamic-title@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4" integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== -"@svgr/babel-plugin-svg-em-dimensions@8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" - integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== - "@svgr/babel-plugin-svg-em-dimensions@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217" integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== -"@svgr/babel-plugin-transform-react-native-svg@8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" - integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== - "@svgr/babel-plugin-transform-react-native-svg@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305" integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== -"@svgr/babel-plugin-transform-svg-component@8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" - integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== - "@svgr/babel-plugin-transform-svg-component@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250" integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== -"@svgr/babel-preset@8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" - integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== - dependencies: - "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" - "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" - "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" - "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" - "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" - "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" - "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" - "@svgr/babel-plugin-transform-svg-component" "8.0.0" - "@svgr/babel-preset@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828" @@ -3193,17 +2298,6 @@ "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" "@svgr/babel-plugin-transform-svg-component" "^6.5.1" -"@svgr/core@8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" - integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== - dependencies: - "@babel/core" "^7.21.3" - "@svgr/babel-preset" "8.1.0" - camelcase "^6.2.0" - cosmiconfig "^8.1.3" - snake-case "^3.0.4" - "@svgr/core@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.5.1.tgz#d3e8aa9dbe3fbd747f9ee4282c1c77a27410488a" @@ -3215,14 +2309,6 @@ camelcase "^6.2.0" cosmiconfig "^7.0.1" -"@svgr/hast-util-to-babel-ast@8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" - integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== - dependencies: - "@babel/types" "^7.21.3" - entities "^4.4.0" - "@svgr/hast-util-to-babel-ast@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2" @@ -3231,16 +2317,6 @@ "@babel/types" "^7.20.0" entities "^4.4.0" -"@svgr/plugin-jsx@8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" - integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== - dependencies: - "@babel/core" "^7.21.3" - "@svgr/babel-preset" "8.1.0" - "@svgr/hast-util-to-babel-ast" "8.0.0" - svg-parser "^2.0.4" - "@svgr/plugin-jsx@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072" @@ -3251,15 +2327,6 @@ "@svgr/hast-util-to-babel-ast" "^6.5.1" svg-parser "^2.0.4" -"@svgr/plugin-svgo@8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz#b115b7b967b564f89ac58feae89b88c3decd0f00" - integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA== - dependencies: - cosmiconfig "^8.1.3" - deepmerge "^4.3.1" - svgo "^3.0.2" - "@svgr/plugin-svgo@^6.5.1": version "6.5.1" resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84" @@ -3283,20 +2350,6 @@ "@svgr/plugin-jsx" "^6.5.1" "@svgr/plugin-svgo" "^6.5.1" -"@svgr/webpack@^8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-8.1.0.tgz#16f1b5346f102f89fda6ec7338b96a701d8be0c2" - integrity sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA== - dependencies: - "@babel/core" "^7.21.3" - "@babel/plugin-transform-react-constant-elements" "^7.21.3" - "@babel/preset-env" "^7.20.2" - "@babel/preset-react" "^7.18.6" - "@babel/preset-typescript" "^7.21.0" - "@svgr/core" "8.1.0" - "@svgr/plugin-jsx" "8.1.0" - "@svgr/plugin-svgo" "8.1.0" - "@szmarczak/http-timer@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" @@ -4019,18 +3072,6 @@ autoprefixer@^10.4.12, autoprefixer@^10.4.14: picocolors "^1.0.0" postcss-value-parser "^4.2.0" -autoprefixer@^10.4.19: - version "10.4.19" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" - integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== - dependencies: - browserslist "^4.23.0" - caniuse-lite "^1.0.30001599" - fraction.js "^4.3.7" - normalize-range "^0.1.2" - picocolors "^1.0.0" - postcss-value-parser "^4.2.0" - babel-loader@^9.1.3: version "9.1.3" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-9.1.3.tgz#3d0e01b4e69760cc694ee306fe16d358aa1c6f9a" @@ -4046,15 +3087,6 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" -babel-plugin-polyfill-corejs2@^0.4.10: - version "0.4.11" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" - integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== - dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.6.2" - semver "^6.3.1" - babel-plugin-polyfill-corejs2@^0.4.8: version "0.4.8" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz#dbcc3c8ca758a290d47c3c6a490d59429b0d2269" @@ -4064,14 +3096,6 @@ babel-plugin-polyfill-corejs2@^0.4.8: "@babel/helper-define-polyfill-provider" "^0.5.0" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.10.4: - version "0.10.4" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" - integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.1" - core-js-compat "^3.36.1" - babel-plugin-polyfill-corejs3@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.9.0.tgz#9eea32349d94556c2ad3ab9b82ebb27d4bf04a81" @@ -4087,13 +3111,6 @@ babel-plugin-polyfill-regenerator@^0.5.5: dependencies: "@babel/helper-define-polyfill-provider" "^0.5.0" -babel-plugin-polyfill-regenerator@^0.6.1: - version "0.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" - integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.2" - bail@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" @@ -4285,11 +3302,6 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001591: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001594.tgz#bea552414cd52c2d0c985ed9206314a696e685f5" integrity sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g== -caniuse-lite@^1.0.30001599: - version "1.0.30001629" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001629.tgz#907a36f4669031bd8a1a8dbc2fa08b29e0db297e" - integrity sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw== - ccount@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" @@ -4466,7 +3478,7 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colord@^2.9.1, colord@^2.9.3: +colord@^2.9.1: version "2.9.3" resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== @@ -4626,13 +3638,6 @@ core-js-compat@^3.31.0, core-js-compat@^3.34.0: dependencies: browserslist "^4.22.3" -core-js-compat@^3.36.1: - version "3.37.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee" - integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== - dependencies: - browserslist "^4.23.0" - core-js-pure@^3.30.2: version "3.36.0" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.36.0.tgz#ffb34330b14e594d6a9835cf5843b4123f1d95db" @@ -4677,7 +3682,7 @@ cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -cosmiconfig@^8.1.3, cosmiconfig@^8.3.5: +cosmiconfig@^8.3.5: version "8.3.6" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== @@ -4708,11 +3713,6 @@ css-declaration-sorter@^6.3.1: resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== -css-declaration-sorter@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz#6dec1c9523bc4a643e088aab8f09e67a54961024" - integrity sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow== - css-loader@^6.8.1: version "6.10.0" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.10.0.tgz#7c172b270ec7b833951b52c348861206b184a4b7" @@ -4739,18 +3739,6 @@ css-minimizer-webpack-plugin@^4.2.2: serialize-javascript "^6.0.0" source-map "^0.6.1" -css-minimizer-webpack-plugin@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz#33effe662edb1a0bf08ad633c32fa75d0f7ec565" - integrity sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg== - dependencies: - "@jridgewell/trace-mapping" "^0.3.18" - cssnano "^6.0.1" - jest-worker "^29.4.3" - postcss "^8.4.24" - schema-utils "^4.0.1" - serialize-javascript "^6.0.1" - css-select@^4.1.3: version "4.3.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" @@ -4781,22 +3769,6 @@ css-tree@^1.1.2, css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" -css-tree@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" - integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== - dependencies: - mdn-data "2.0.30" - source-map-js "^1.0.1" - -css-tree@~2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" - integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== - dependencies: - mdn-data "2.0.28" - source-map-js "^1.0.1" - css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" @@ -4819,19 +3791,6 @@ cssnano-preset-advanced@^5.3.10: postcss-reduce-idents "^5.2.0" postcss-zindex "^5.1.0" -cssnano-preset-advanced@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz#82b090872b8f98c471f681d541c735acf8b94d3f" - integrity sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ== - dependencies: - autoprefixer "^10.4.19" - browserslist "^4.23.0" - cssnano-preset-default "^6.1.2" - postcss-discard-unused "^6.0.5" - postcss-merge-idents "^6.0.3" - postcss-reduce-idents "^6.0.3" - postcss-zindex "^6.0.2" - cssnano-preset-default@^5.2.14: version "5.2.14" resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" @@ -4867,52 +3826,11 @@ cssnano-preset-default@^5.2.14: postcss-svgo "^5.1.0" postcss-unique-selectors "^5.1.1" -cssnano-preset-default@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz#adf4b89b975aa775f2750c89dbaf199bbd9da35e" - integrity sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg== - dependencies: - browserslist "^4.23.0" - css-declaration-sorter "^7.2.0" - cssnano-utils "^4.0.2" - postcss-calc "^9.0.1" - postcss-colormin "^6.1.0" - postcss-convert-values "^6.1.0" - postcss-discard-comments "^6.0.2" - postcss-discard-duplicates "^6.0.3" - postcss-discard-empty "^6.0.3" - postcss-discard-overridden "^6.0.2" - postcss-merge-longhand "^6.0.5" - postcss-merge-rules "^6.1.1" - postcss-minify-font-values "^6.1.0" - postcss-minify-gradients "^6.0.3" - postcss-minify-params "^6.1.0" - postcss-minify-selectors "^6.0.4" - postcss-normalize-charset "^6.0.2" - postcss-normalize-display-values "^6.0.2" - postcss-normalize-positions "^6.0.2" - postcss-normalize-repeat-style "^6.0.2" - postcss-normalize-string "^6.0.2" - postcss-normalize-timing-functions "^6.0.2" - postcss-normalize-unicode "^6.1.0" - postcss-normalize-url "^6.0.2" - postcss-normalize-whitespace "^6.0.2" - postcss-ordered-values "^6.0.2" - postcss-reduce-initial "^6.1.0" - postcss-reduce-transforms "^6.0.2" - postcss-svgo "^6.0.3" - postcss-unique-selectors "^6.0.4" - cssnano-utils@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== -cssnano-utils@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.2.tgz#56f61c126cd0f11f2eef1596239d730d9fceff3c" - integrity sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ== - cssnano@^5.1.15, cssnano@^5.1.8: version "5.1.15" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" @@ -4922,14 +3840,6 @@ cssnano@^5.1.15, cssnano@^5.1.8: lilconfig "^2.0.3" yaml "^1.10.2" -cssnano@^6.0.1, cssnano@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.1.2.tgz#4bd19e505bd37ee7cf0dc902d3d869f6d79c66b8" - integrity sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA== - dependencies: - cssnano-preset-default "^6.1.2" - lilconfig "^3.1.1" - csso@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" @@ -4937,13 +3847,6 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" -csso@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" - integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== - dependencies: - css-tree "~2.2.0" - csstype@^3.0.2: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" @@ -5286,7 +4189,7 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deepmerge@^4.2.2, deepmerge@^4.3.1: +deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -5987,7 +4890,7 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^11.1.1, fs-extra@^11.2.0: +fs-extra@^11.1.1: version "11.2.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== @@ -6888,7 +5791,7 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -jest-worker@^29.1.2, jest-worker@^29.4.3: +jest-worker@^29.1.2: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== @@ -7049,11 +5952,6 @@ lilconfig@^2.0.3: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== -lilconfig@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.1.tgz#9d8a246fa753106cfc205fd2d77042faca56e5e3" - integrity sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ== - lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -7435,16 +6333,6 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== -mdn-data@2.0.28: - version "2.0.28" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" - integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== - -mdn-data@2.0.30: - version "2.0.30" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" - integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -8601,14 +7489,6 @@ postcss-calc@^8.2.3: postcss-selector-parser "^6.0.9" postcss-value-parser "^4.2.0" -postcss-calc@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" - integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ== - dependencies: - postcss-selector-parser "^6.0.11" - postcss-value-parser "^4.2.0" - postcss-colormin@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" @@ -8619,16 +7499,6 @@ postcss-colormin@^5.3.1: colord "^2.9.1" postcss-value-parser "^4.2.0" -postcss-colormin@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.1.0.tgz#076e8d3fb291fbff7b10e6b063be9da42ff6488d" - integrity sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw== - dependencies: - browserslist "^4.23.0" - caniuse-api "^3.0.0" - colord "^2.9.3" - postcss-value-parser "^4.2.0" - postcss-convert-values@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" @@ -8637,54 +7507,26 @@ postcss-convert-values@^5.1.3: browserslist "^4.21.4" postcss-value-parser "^4.2.0" -postcss-convert-values@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz#3498387f8efedb817cbc63901d45bd1ceaa40f48" - integrity sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w== - dependencies: - browserslist "^4.23.0" - postcss-value-parser "^4.2.0" - postcss-discard-comments@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== -postcss-discard-comments@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz#e768dcfdc33e0216380623652b0a4f69f4678b6c" - integrity sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw== - postcss-discard-duplicates@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== -postcss-discard-duplicates@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz#d121e893c38dc58a67277f75bb58ba43fce4c3eb" - integrity sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw== - postcss-discard-empty@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== -postcss-discard-empty@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz#ee39c327219bb70473a066f772621f81435a79d9" - integrity sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ== - postcss-discard-overridden@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== -postcss-discard-overridden@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz#4e9f9c62ecd2df46e8fdb44dc17e189776572e2d" - integrity sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ== - postcss-discard-unused@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz#8974e9b143d887677304e558c1166d3762501142" @@ -8692,13 +7534,6 @@ postcss-discard-unused@^5.1.0: dependencies: postcss-selector-parser "^6.0.5" -postcss-discard-unused@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz#c1b0e8c032c6054c3fbd22aaddba5b248136f338" - integrity sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA== - dependencies: - postcss-selector-parser "^6.0.16" - postcss-loader@^7.3.3: version "7.3.4" resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.4.tgz#aed9b79ce4ed7e9e89e56199d25ad1ec8f606209" @@ -8716,14 +7551,6 @@ postcss-merge-idents@^5.1.1: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" -postcss-merge-idents@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz#7b9c31c7bc823c94bec50f297f04e3c2b838ea65" - integrity sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g== - dependencies: - cssnano-utils "^4.0.2" - postcss-value-parser "^4.2.0" - postcss-merge-longhand@^5.1.7: version "5.1.7" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" @@ -8732,14 +7559,6 @@ postcss-merge-longhand@^5.1.7: postcss-value-parser "^4.2.0" stylehacks "^5.1.1" -postcss-merge-longhand@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz#ba8a8d473617c34a36abbea8dda2b215750a065a" - integrity sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w== - dependencies: - postcss-value-parser "^4.2.0" - stylehacks "^6.1.1" - postcss-merge-rules@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" @@ -8750,16 +7569,6 @@ postcss-merge-rules@^5.1.4: cssnano-utils "^3.1.0" postcss-selector-parser "^6.0.5" -postcss-merge-rules@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz#7aa539dceddab56019469c0edd7d22b64c3dea9d" - integrity sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ== - dependencies: - browserslist "^4.23.0" - caniuse-api "^3.0.0" - cssnano-utils "^4.0.2" - postcss-selector-parser "^6.0.16" - postcss-minify-font-values@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" @@ -8767,13 +7576,6 @@ postcss-minify-font-values@^5.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-minify-font-values@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz#a0e574c02ee3f299be2846369211f3b957ea4c59" - integrity sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg== - dependencies: - postcss-value-parser "^4.2.0" - postcss-minify-gradients@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" @@ -8783,15 +7585,6 @@ postcss-minify-gradients@^5.1.1: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" -postcss-minify-gradients@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz#ca3eb55a7bdb48a1e187a55c6377be918743dbd6" - integrity sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q== - dependencies: - colord "^2.9.3" - cssnano-utils "^4.0.2" - postcss-value-parser "^4.2.0" - postcss-minify-params@^5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" @@ -8801,15 +7594,6 @@ postcss-minify-params@^5.1.4: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" -postcss-minify-params@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz#54551dec77b9a45a29c3cb5953bf7325a399ba08" - integrity sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA== - dependencies: - browserslist "^4.23.0" - cssnano-utils "^4.0.2" - postcss-value-parser "^4.2.0" - postcss-minify-selectors@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" @@ -8817,13 +7601,6 @@ postcss-minify-selectors@^5.2.1: dependencies: postcss-selector-parser "^6.0.5" -postcss-minify-selectors@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz#197f7d72e6dd19eed47916d575d69dc38b396aff" - integrity sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ== - dependencies: - postcss-selector-parser "^6.0.16" - postcss-modules-extract-imports@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" @@ -8857,11 +7634,6 @@ postcss-normalize-charset@^5.1.0: resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== -postcss-normalize-charset@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz#1ec25c435057a8001dac942942a95ffe66f721e1" - integrity sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ== - postcss-normalize-display-values@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" @@ -8869,13 +7641,6 @@ postcss-normalize-display-values@^5.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-display-values@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz#54f02764fed0b288d5363cbb140d6950dbbdd535" - integrity sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg== - dependencies: - postcss-value-parser "^4.2.0" - postcss-normalize-positions@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" @@ -8883,13 +7648,6 @@ postcss-normalize-positions@^5.1.1: dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-positions@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz#e982d284ec878b9b819796266f640852dbbb723a" - integrity sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q== - dependencies: - postcss-value-parser "^4.2.0" - postcss-normalize-repeat-style@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" @@ -8897,13 +7655,6 @@ postcss-normalize-repeat-style@^5.1.1: dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz#f8006942fd0617c73f049dd8b6201c3a3040ecf3" - integrity sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ== - dependencies: - postcss-value-parser "^4.2.0" - postcss-normalize-string@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" @@ -8911,13 +7662,6 @@ postcss-normalize-string@^5.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-string@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz#e3cc6ad5c95581acd1fc8774b309dd7c06e5e363" - integrity sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ== - dependencies: - postcss-value-parser "^4.2.0" - postcss-normalize-timing-functions@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" @@ -8925,13 +7669,6 @@ postcss-normalize-timing-functions@^5.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz#40cb8726cef999de984527cbd9d1db1f3e9062c0" - integrity sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA== - dependencies: - postcss-value-parser "^4.2.0" - postcss-normalize-unicode@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" @@ -8940,14 +7677,6 @@ postcss-normalize-unicode@^5.1.1: browserslist "^4.21.4" postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz#aaf8bbd34c306e230777e80f7f12a4b7d27ce06e" - integrity sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg== - dependencies: - browserslist "^4.23.0" - postcss-value-parser "^4.2.0" - postcss-normalize-url@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" @@ -8956,13 +7685,6 @@ postcss-normalize-url@^5.1.0: normalize-url "^6.0.1" postcss-value-parser "^4.2.0" -postcss-normalize-url@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz#292792386be51a8de9a454cb7b5c58ae22db0f79" - integrity sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ== - dependencies: - postcss-value-parser "^4.2.0" - postcss-normalize-whitespace@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" @@ -8970,13 +7692,6 @@ postcss-normalize-whitespace@^5.1.1: dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz#fbb009e6ebd312f8b2efb225c2fcc7cf32b400cd" - integrity sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q== - dependencies: - postcss-value-parser "^4.2.0" - postcss-ordered-values@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" @@ -8985,14 +7700,6 @@ postcss-ordered-values@^5.1.3: cssnano-utils "^3.1.0" postcss-value-parser "^4.2.0" -postcss-ordered-values@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz#366bb663919707093451ab70c3f99c05672aaae5" - integrity sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q== - dependencies: - cssnano-utils "^4.0.2" - postcss-value-parser "^4.2.0" - postcss-reduce-idents@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz#c89c11336c432ac4b28792f24778859a67dfba95" @@ -9000,13 +7707,6 @@ postcss-reduce-idents@^5.2.0: dependencies: postcss-value-parser "^4.2.0" -postcss-reduce-idents@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz#b0d9c84316d2a547714ebab523ec7d13704cd486" - integrity sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA== - dependencies: - postcss-value-parser "^4.2.0" - postcss-reduce-initial@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" @@ -9015,14 +7715,6 @@ postcss-reduce-initial@^5.1.2: browserslist "^4.21.4" caniuse-api "^3.0.0" -postcss-reduce-initial@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz#4401297d8e35cb6e92c8e9586963e267105586ba" - integrity sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw== - dependencies: - browserslist "^4.23.0" - caniuse-api "^3.0.0" - postcss-reduce-transforms@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" @@ -9030,21 +7722,6 @@ postcss-reduce-transforms@^5.1.0: dependencies: postcss-value-parser "^4.2.0" -postcss-reduce-transforms@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz#6fa2c586bdc091a7373caeee4be75a0f3e12965d" - integrity sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.16: - version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz#49694cb4e7c649299fea510a29fa6577104bcf53" - integrity sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: version "6.0.15" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" @@ -9060,13 +7737,6 @@ postcss-sort-media-queries@^4.4.1: dependencies: sort-css-media-queries "2.1.0" -postcss-sort-media-queries@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz#4556b3f982ef27d3bac526b99b6c0d3359a6cf97" - integrity sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA== - dependencies: - sort-css-media-queries "2.2.0" - postcss-svgo@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" @@ -9075,14 +7745,6 @@ postcss-svgo@^5.1.0: postcss-value-parser "^4.2.0" svgo "^2.7.0" -postcss-svgo@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.3.tgz#1d6e180d6df1fa8a3b30b729aaa9161e94f04eaa" - integrity sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g== - dependencies: - postcss-value-parser "^4.2.0" - svgo "^3.2.0" - postcss-unique-selectors@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" @@ -9090,13 +7752,6 @@ postcss-unique-selectors@^5.1.1: dependencies: postcss-selector-parser "^6.0.5" -postcss-unique-selectors@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz#983ab308896b4bf3f2baaf2336e14e52c11a2088" - integrity sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg== - dependencies: - postcss-selector-parser "^6.0.16" - postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" @@ -9107,11 +7762,6 @@ postcss-zindex@^5.1.0: resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-5.1.0.tgz#4a5c7e5ff1050bd4c01d95b1847dfdcc58a496ff" integrity sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A== -postcss-zindex@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-6.0.2.tgz#e498304b83a8b165755f53db40e2ea65a99b56e1" - integrity sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg== - postcss@^8.4.17, postcss@^8.4.21, postcss@^8.4.26, postcss@^8.4.33: version "8.4.35" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7" @@ -9121,15 +7771,6 @@ postcss@^8.4.17, postcss@^8.4.21, postcss@^8.4.26, postcss@^8.4.33: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.24, postcss@^8.4.38: - version "8.4.38" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" - integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== - dependencies: - nanoid "^3.3.7" - picocolors "^1.0.0" - source-map-js "^1.2.0" - pretty-error@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" @@ -9367,13 +8008,6 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: "@types/react" "*" prop-types "^15.6.2" -"react-loadable@npm:@docusaurus/react-loadable@6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz#de6c7f73c96542bd70786b8e522d535d69069dc4" - integrity sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ== - dependencies: - "@types/react" "*" - react-router-config@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" @@ -9774,7 +8408,7 @@ schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0, schema-utils@^4.0.1: +schema-utils@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== @@ -10003,14 +8637,6 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== -snake-case@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" - integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== - dependencies: - dot-case "^3.0.4" - tslib "^2.0.3" - sockjs@^0.3.24: version "0.3.24" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" @@ -10025,16 +8651,6 @@ sort-css-media-queries@2.1.0: resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz#7c85e06f79826baabb232f5560e9745d7a78c4ce" integrity sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA== -sort-css-media-queries@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz#aa33cf4a08e0225059448b6c40eddbf9f1c8334c" - integrity sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA== - -source-map-js@^1.0.1, source-map-js@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" - integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== - source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -10221,14 +8837,6 @@ stylehacks@^5.1.1: browserslist "^4.21.4" postcss-selector-parser "^6.0.4" -stylehacks@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.1.1.tgz#543f91c10d17d00a440430362d419f79c25545a6" - integrity sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg== - dependencies: - browserslist "^4.23.0" - postcss-selector-parser "^6.0.16" - stylis@^4.1.3: version "4.3.1" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.1.tgz#ed8a9ebf9f76fe1e12d462f5cc3c4c980b23a7eb" @@ -10278,19 +8886,6 @@ svgo@^2.7.0, svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" -svgo@^3.0.2, svgo@^3.2.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8" - integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^5.1.0" - css-tree "^2.3.1" - css-what "^6.1.0" - csso "^5.0.5" - picocolors "^1.0.0" - tapable@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" diff --git a/packages/prover/README.md b/packages/prover/README.md index b98340698bbe..a2a04123cd76 100644 --- a/packages/prover/README.md +++ b/packages/prover/README.md @@ -74,7 +74,7 @@ lodestar-prover proxy \ There can be different implementations of the web3 providers and each can handle the RPC request differently. We call those different provider types. We had provided builtin support for common providers e.g. web3.js, ethers or any eip1193 compatible providers. We inspect given provider instance at runtime to detect the correct provider type. -If your project is using some provider type which is not among above list, you have the option to register a custom provider type with the `createVerifiedExecutionProvider` with the option `providerTypes` which will be an array of your supported provider types. Your custom provider types will have higher priority than default provider types. Please see [existing provide types implementations](./src/provider_types/) to know how to implement your own if needed. +If your project is using some provider type which is not among above list, you have the option to register a custom provider type with the `createVerifiedExecutionProvider` with the option `providerTypes` which will be an array of your supported provider types. Your custom provider types will have higher priority than default provider types. Please see [existing provide types implementations](https://github.com/ChainSafe/lodestar/tree/unstable/packages/prover/src/provider_types) to know how to implement your own if needed. ## Supported Web3 Methods