diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000000..ba02b753af34 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 46aca2515c83..000000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,275 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - es6: true, - node: true, - // Performance tests still use mocha - mocha: true, - }, - globals: { - BigInt: true, - }, - parser: "@typescript-eslint/parser", - parserOptions: { - ecmaVersion: 10, - project: "./tsconfig.json", - sourceType: "module", - }, - ignorePatterns: [ - "webEsmBundle.browser.test.ts" - ], - plugins: ["@typescript-eslint", "eslint-plugin-import", "@chainsafe/eslint-plugin-node", "prettier"], - extends: [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/errors", - "plugin:import/typescript", - "plugin:import/warnings", - ], - rules: { - "@chainsafe/node/file-extension-in-import": ["error", "always", {esm: true}], - "@chainsafe/node/no-deprecated-api": "error", - "@typescript-eslint/await-thenable": "error", - "@typescript-eslint/ban-ts-comment": "error", - "@typescript-eslint/explicit-function-return-type": ["error", {allowExpressions: true}], - "@typescript-eslint/explicit-member-accessibility": ["error", {accessibility: "no-public"}], - "@typescript-eslint/func-call-spacing": "error", - // TODO after upgrading es-lint, member-ordering is now leading to lint errors. Set to warning now and fix in another PR - "@typescript-eslint/member-ordering": "off", - "@typescript-eslint/naming-convention": [ - "error", - {selector: "default", format: ["camelCase"]}, - { - selector: ["classProperty", "objectLiteralProperty", "classMethod", "parameter"], - format: ["camelCase"], - leadingUnderscore: "allow", - }, - //variable must be in camel or upper case - {selector: "variable", format: ["camelCase", "UPPER_CASE"], leadingUnderscore: "allow"}, - //classes and types must be in PascalCase - {selector: ["typeLike", "enum"], format: ["PascalCase"]}, - {selector: "enumMember", format: null}, - //ignore rule for quoted stuff - { - selector: [ - "classProperty", - "objectLiteralProperty", - "typeProperty", - "classMethod", - "objectLiteralMethod", - "typeMethod", - "accessor", - "enumMember", - ], - format: null, - modifiers: ["requiresQuotes"], - }, - //ignore rules on destructured params - {selector: "variable", modifiers: ["destructured"], format: null}, - { - selector: "import", - format: ["camelCase", "PascalCase"], - }, - ], - "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/no-floating-promises": "error", - "@typescript-eslint/no-non-null-assertion": "error", - "@typescript-eslint/no-require-imports": "error", - // We usually type-cast these standard types because the concerned function accepts any type - // and we want to TS detect error if original variable type changes - "@typescript-eslint/no-unnecessary-type-assertion": ["error", {typesToIgnore: ["string", "bigint", "number"]}], - "@typescript-eslint/no-unsafe-assignment": "error", - "@typescript-eslint/no-unsafe-call": "error", - "@typescript-eslint/no-unsafe-member-access": "error", - "@typescript-eslint/no-unsafe-return": "error", - "@typescript-eslint/no-unused-expressions": "error", - "@typescript-eslint/no-unused-vars": ["error", {varsIgnorePattern: "^_", argsIgnorePattern: "^_"}], - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/restrict-template-expressions": [ - "error", - {allowNumber: true, allowBoolean: true, allowNullish: true, allowNever: true, allowRegExp: true}, - ], - "@typescript-eslint/return-await": "error", - "@typescript-eslint/semi": "error", - "@typescript-eslint/strict-boolean-expressions": [ - "error", - {allowNullableBoolean: true, allowNullableString: true, allowAny: true}, - ], - - "@typescript-eslint/type-annotation-spacing": "error", - "constructor-super": "off", - "func-call-spacing": "off", - // Force to add names to all functions to ease CPU profiling - "func-names": ["error", "always"], - "import/namespace": "off", - //if --fix is run it messes imports like /lib/presets/minimal & /lib/presets/mainnet - "import/no-duplicates": "off", - "import/no-extraneous-dependencies": [ - "error", - { - devDependencies: false, - optionalDependencies: false, - peerDependencies: false, - }, - ], - "import/no-relative-packages": "error", - // TEMP Disabled while eslint-plugin-import support ESM (Typescript does support it) https://github.com/import-js/eslint-plugin-import/issues/2170 - "import/no-unresolved": "off", - "import/order": [ - "error", - { - groups: ["builtin", "external", "internal", "parent", "sibling", "index"], - pathGroups: [ - {pattern: "@lodestar/**", group: "internal"}, - // We want mocks to be imported before any internal code - {pattern: "**/mocks/**", group: "internal"}, - ], - pathGroupsExcludedImportTypes: ["builtin"], - }, - ], - //doesnt work, it reports false errors - "new-parens": "error", - "no-bitwise": "off", - "no-caller": "error", - "no-cond-assign": "error", - "no-consecutive-blank-lines": 0, - "no-console": "error", - "no-loss-of-precision": "error", - "no-prototype-builtins": 0, - "no-restricted-globals": [ - "error", - { - name: "fetch", - message: "Please use 'fetch' from '@lodestar/api' instead.", - }, - ], - "no-restricted-imports": [ - "error", - { - patterns: ["../lib/*", "@chainsafe/*/lib/*"], - paths: [ - ...restrictNodeModuleImports( - "child_process", - "crypto", - "fs", - "http", - "net", - "os", - "path", - "stream", - "util", - "url", - "worker_threads" - ), - ], - }, - ], - "no-restricted-syntax": ["error", ...restrictImportDestructuring("node:fs", "node:os", "node:path")], - // superseded by @typescript-eslint/return-await, must be disabled as it can report incorrect errors - "no-return-await": "off", - "no-var": "error", - "object-curly-spacing": ["error", "never"], - "object-literal-sort-keys": 0, - "prefer-const": "error", - "prettier/prettier": "error", - quotes: ["error", "double"], - semi: "off", - }, - settings: { - "import/core-modules": [ - "node:child_process", - "node:crypto", - "node:fs", - "node:http", - "node:net", - "node:os", - "node:path", - "node:stream", - "node:util", - "node:url", - ], - "import/resolver": { - typescript: { - project: "packages/*/tsconfig.json", - }, - }, - }, - overrides: [ - { - files: [ - "**/*.config.js", - "**/*.config.mjs", - "**/*.config.cjs", - "**/*.config.ts", - "scripts/vitest/**/*.ts", - "scripts/vite/**/*.ts", - ], - rules: { - "@typescript-eslint/naming-convention": "off", - // Allow require in CJS modules - "@typescript-eslint/no-require-imports": "off", - // Allow require in CJS modules - "@typescript-eslint/no-var-requires": "off", - // Allow importing packages from dev dependencies - "import/no-extraneous-dependencies": "off", - // Allow importing and mixing different configurations - "import/no-relative-packages": "off", - }, - }, - { - files: ["**/test/**/*.ts"], - rules: { - "@typescript-eslint/no-explicit-any": "off", - "func-names": "off", - "import/no-extraneous-dependencies": "off", - // Turned off as it floods log with warnings. Underlying issue is not critical so switching off is acceptable - "import/no-named-as-default-member": "off", - }, - }, - { - files: ["**/perf/**/*.ts"], - rules: { - // A lot of benchmarks just need to execute expressions without using the result - "@typescript-eslint/no-unused-expressions": "off", - }, - }, - { - files: ["**/test/**/*.test.ts"], - plugins: ["vitest"], - extends: ["plugin:vitest/recommended"], - rules: { - "vitest/consistent-test-it": ["error", {fn: "it", withinDescribe: "it"}], - // We use a lot dynamic assertions so tests may not have usage of expect - "vitest/expect-expect": "off", - "vitest/no-disabled-tests": "warn", - "vitest/no-focused-tests": "error", - "vitest/prefer-called-with": "error", - "vitest/prefer-spy-on": "error", - // Our usage contains dynamic test title, this rule enforce static string value - "vitest/valid-title": "off", - }, - }, - { - files: ["**/types/**/*.ts"], - rules: { - "@typescript-eslint/naming-convention": [ - "off", - {selector: "interface", prefix: ["I"]}, - {selector: "interface", format: ["PascalCase"], prefix: ["I"]}, - ], - }, - }, - ], -}; - -function restrictNodeModuleImports(...modules) { - return modules.map((module) => ({name: module, message: `Please use 'node:${module}' instead.`})); -} - -function restrictImportDestructuring(...modules) { - return modules.map((module) => ({ - selector: `ImportDeclaration[source.value='${module}'] ImportSpecifier`, - message: `Importing from '${module}' using destructuring is restricted.`, - })); -} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c801b0462d60..5514f6e896b7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,9 +64,6 @@ jobs: - name: Assert ESM module exports run: node scripts/assert_exports.mjs - - name: Assert eslintrc rules sorted - run: scripts/assert_eslintrc_sorted.mjs - type-checks: name: Type Checks needs: build diff --git a/.gitignore b/.gitignore index 073b21cf322a..52d9bc66e5b6 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ validators .tmp .npmrc .vscode/launch.json -.vscode/settings.json +!.vscode/settings.json .vscode/tasks.json # Tests artifacts diff --git a/.prettierignore b/.prettierignore index 7871bd5d3ad7..3934e7720067 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,6 @@ **/.nyc_output /packages/*/spec-tests node_modules -**/node_modules \ No newline at end of file +**/node_modules +*.js +*.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000000..d2b074c6eea1 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "biomejs.biome", + "esbenp.prettier-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000000..fbc0552fe61c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "window.title": "${activeEditorShort}${separator}${rootName}${separator}${profileName}${separator}[${activeRepositoryBranchName}]", + "editor.defaultFormatter": "esbenp.prettier-vscode", + // For `sysoev.vscode-open-in-github` extension + "openInGitHub.defaultBranch": "unstable", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.biome": "explicit", + }, + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[json]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[jsonc]": { + "editor.defaultFormatter": "biomejs.biome" + } +} diff --git a/.wordlist.txt b/.wordlist.txt index 11a5a3746476..a2e642a926a0 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -26,7 +26,6 @@ EIPs EL ENR ENRs -ESLint ETH Edgington Erigon diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9592f3bb0237..24d087810980 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ To run tests: - :test_tube: Run `yarn test:spec` for spec tests. - :test_tube: Run `yarn test` to run all tests. - :test_tube: Run `yarn check-types` to check TypeScript types. -- :test_tube: Run `yarn lint` to run the linter (ESLint). +- :test_tube: Run `yarn lint` to run the linter. Note that to run `test:e2e`, first ensure that the environment is correctly setup by running the `run_e2e_env.sh` script. This script requires a running docker engine. diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 000000000000..212a61d86aa2 --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,297 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", + "vcs": { + "clientKind": "git", + "enabled": true, + "useIgnoreFile": true + }, + "files": { + "include": ["packages/*/src/**/*.ts", "packages/*/test/**/*.ts"] + }, + "formatter": { + "enabled": true, + "formatWithErrors": true, + "useEditorconfig": true, + "lineWidth": 120, + "attributePosition": "auto", + "bracketSpacing": false, + "ignore": ["**/lib", "**/.nyc_output", "./packages/*/spec-tests", "**/node_modules", "./packages/*/node_modules/**"] + }, + "organizeImports": { + // TODO: We will enable this settings as soon mono-repo support is provided in biome. + // Currently it didn't recognize local packages in repo and sort those higher than npm packages + // https://github.com/biomejs/biome/issues/2228 + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error", + "useImportExtensions": { + "level": "error", + "options": { + "suggestedExtensions": { + "ts": { + "module": "js", + "component": "jsx" + } + } + } + }, + "useArrayLiterals": "error", + "noUndeclaredVariables": "error" + }, + "performance": { + // This rule should be enabled but with considerations and careful review + "noDelete": "off" + }, + "style": { + // The code usage looks suspicious so it should be enabled in a separate PR + "noCommaOperator": "off", + // There are a lot of places we mutate params, should be fixed in an independent PR. + "noParameterAssign": "off", + "noRestrictedGlobals": { + "level": "error", + "options": { + "deniedGlobals": ["fetch"] + } + }, + // We prefer to use `Math.pow` over `**` operator + "useExponentiationOperator": "off", + // In some cases the enums are initialized with values of other enums + "useLiteralEnumMembers": "off", + // We prefer to have multiple declarations lines + "useSingleVarDeclarator": "off", + // We use `+` operator for string concatenation a lot + "useTemplate": "off", + // We use to export types and object without differentiating + "useExportType": "off", + // We use to import types and object without differentiating + "useImportType": "off", + // It's nice to use `Number` namespace but should be done in a separate PR + "useNumberNamespace": "off", + // We prefer to auto-initialize enums + "useEnumInitializers": "off", + // Namespaces are deprecated way to organize modules in TS + "noNamespace": "error", + "useNamingConvention": { + "level": "error", + "options": { + "strictCase": false, + "requireAscii": true, + "conventions": [ + // Skip __dirname and any variable starting with _, for rest check next convention + { + "selector": { + "kind": "variable" + }, + "match": "(?:__dirname)|(?:_.*)|(.*)" + }, + { + "selector": { + "kind": "variable" + }, + "formats": ["camelCase", "PascalCase", "CONSTANT_CASE"] + }, + { + "selector": { + "kind": "typeLike" + }, + "formats": ["camelCase", "snake_case", "PascalCase", "CONSTANT_CASE"] + }, + { + "selector": { + "kind": "enum" + }, + "formats": ["PascalCase"] + }, + { + "selector": { + "kind": "objectLiteralProperty" + }, + "formats": ["camelCase", "snake_case", "PascalCase", "CONSTANT_CASE"] + }, + { + "selector": { + "kind": "objectLiteralMethod" + }, + "formats": ["camelCase", "snake_case", "PascalCase", "CONSTANT_CASE"] + }, + // Skip any property starting with _ and then check for next convention + { + "selector": { + "kind": "classProperty" + }, + "match": "(?:_.*)|(.*)" + }, + { + "selector": { + "kind": "classProperty" + }, + "formats": ["camelCase", "snake_case", "PascalCase", "CONSTANT_CASE"] + }, + { + "selector": { + "kind": "typeProperty" + }, + "formats": ["camelCase", "snake_case", "PascalCase", "CONSTANT_CASE"] + }, + { + "selector": { + "kind": "typeMethod" + }, + "formats": ["camelCase", "snake_case", "PascalCase", "CONSTANT_CASE"] + }, + { + "selector": { + "kind": "enumMember" + }, + "formats": ["camelCase", "snake_case", "PascalCase", "CONSTANT_CASE"] + }, + { + "selector": { + "kind": "indexParameter" + }, + "formats": ["camelCase", "PascalCase"] + }, + { + "selector": { + "kind": "function" + }, + "formats": ["camelCase", "PascalCase"] + } + ] + } + } + }, + "suspicious": { + // `void` as type is useful in our case when used as generic constraint e.g. K extends number | void + "noConfusingVoidType": "off", + // There is a lot of empty code blocks, should be enabled and clean up separately. + "noEmptyBlockStatements": "off", + "noConsoleLog": "error" + }, + "nursery": { + "useConsistentMemberAccessibility": { + "level": "error", + "options": { + "accessibility": "noPublic" + } + }, + "noCommonJs": "error", + "noRestrictedImports": { + "level": "error", + "options": { + "paths": { + "child_process": "Please use node:child_process instead.", + "crypto": "Please use node:crypto instead.", + "fs": "Please use node:fs instead.", + "http": "Please use node:https instead.", + "net": "Please use node:net instead.", + "os": "Please use node:os instead.", + "path": "Please use node:path instead.", + "stream": "Please use node:stream instead.", + "util": "Please use node:util instead.", + "url": "Please use node:url instead.", + "worker_threads": "Please use node:worker_threads instead." + } + } + }, + "noDuplicateElseIf": "error", + "noUselessEscapeInRegex": "error", + "noIrregularWhitespace": "error", + "noOctalEscape": "error", + // Need to enable this rule with exception to anonymous functions + "useExplicitFunctionReturnType": "off" + } + } + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "es5", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSpacing": false, + "bracketSameLine": false, + "quoteStyle": "double", + "attributePosition": "auto", + "enabled": true + }, + "linter": { + "enabled": true + }, + "globals": ["BigInt"] + }, + "overrides": [ + // Code using console output + { + "include": ["packages/cli/src/", "packages/test-utils/src", "packages/flare/src"], + "linter": { + "rules": { + "suspicious": { + "noConsoleLog": "off" + } + } + } + }, + // All test files + { + "include": ["**/test/**/*.ts", "packages/spec-test-util/src"], + "linter": { + "rules": { + "complexity": { + // During tests we often need to use private/protected attributes, which is only possible with literal keys + "useLiteralKeys": "off" + }, + "suspicious": { + // During tests it's quicker to define variables with `let` without specifying types + "noImplicitAnyLet": "off", + // During testing we use `any` type for quick assignments + "noExplicitAny": "off", + // Console logging is often used in tests + "noConsoleLog": "off" + } + } + } + }, + // Dependencies still using mocha + { + "include": ["packages/**/test/perf/**/*.test.ts", "packages/state-transition/test/utils/beforeValueMocha.ts"], + "javascript": { + // These are used by mocha + "globals": ["describe", "it", "before", "after"] + } + }, + { + "include": [ + // These files are using mix cases e.g. `engine_newPayloadV4` + // It's a mix of snake_case and camelCase, which can't validated by biome + "packages/beacon-node/src/db/buckets.ts", + "packages/beacon-node/src/execution/engine/mock.ts", + "packages/beacon-node/src/execution/engine/types.ts", + "packages/beacon-node/src/eth1/provider/eth1Provider.ts", + "packages/validator/src/buckets.ts", + "packages/prover/src/types.ts", + "prover/src/utils/process.ts", + "prover/src/verified_requests/**/*.ts", + "packages/types/src/utils/**/*.ts", + // This file is using snake_case function names + "packages/beacon-node/test/spec/bls/bls.ts" + ], + "linter": { + "rules": { + "style": { + "useNamingConvention": { + "level": "off", + "options": {} + } + } + } + } + } + ] +} diff --git a/dashboards/lodestar_block_processor.json b/dashboards/lodestar_block_processor.json index 3258efa72fc0..a945916308ab 100644 --- a/dashboards/lodestar_block_processor.json +++ b/dashboards/lodestar_block_processor.json @@ -3985,10 +3985,10 @@ "calcs": [], "displayMode": "list", "placement": "bottom", - "showLegend": false + "showLegend": true }, "tooltip": { - "mode": "single", + "mode": "multi", "sort": "none" }, "tooltipOptions": { @@ -4001,10 +4001,12 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "editorMode": "code", "exemplar": false, "expr": "rate(beacon_fork_choice_find_head_seconds_sum[$rate_interval])/rate(beacon_fork_choice_find_head_seconds_count[$rate_interval])", "interval": "", - "legendFormat": "find head", + "legendFormat": "{{caller}}", + "range": true, "refId": "A" } ], @@ -4103,10 +4105,12 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "editorMode": "code", "exemplar": false, "expr": "beacon_fork_choice_errors_total", "interval": "", "legendFormat": "", + "range": true, "refId": "A" } ], @@ -4189,10 +4193,10 @@ "calcs": [], "displayMode": "list", "placement": "bottom", - "showLegend": true + "showLegend": false }, "tooltip": { - "mode": "single", + "mode": "multi", "sort": "none" }, "tooltipOptions": { @@ -4205,10 +4209,12 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "editorMode": "code", "exemplar": false, "expr": "12 * rate(beacon_fork_choice_find_head_seconds_count[$rate_interval])", "interval": "", - "legendFormat": "updateHead calls", + "legendFormat": "{{caller}}_updateHead_calls", + "range": true, "refId": "A" }, { @@ -4228,11 +4234,13 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "editorMode": "code", "exemplar": false, "expr": "rate(beacon_fork_choice_find_head_seconds_sum[$rate_interval])", "hide": false, "interval": "", - "legendFormat": "usage rate", + "legendFormat": "{{caller}}_usage_rate", + "range": true, "refId": "C" } ], diff --git a/dashboards/lodestar_block_production.json b/dashboards/lodestar_block_production.json index 58c7ba3f3684..04201c62ae4a 100644 --- a/dashboards/lodestar_block_production.json +++ b/dashboards/lodestar_block_production.json @@ -90,6 +90,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -252,7 +253,6 @@ } ], "title": "Full block production avg time with steps", - "transformations": [], "type": "timeseries" }, { @@ -266,6 +266,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -428,7 +429,6 @@ } ], "title": "Blinded block production avg time with steps", - "transformations": [], "type": "timeseries" }, { @@ -442,14 +442,15 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", - "axisLabel": "", + "axisLabel": "builder | engine", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", + "fillOpacity": 30, + "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, @@ -457,6 +458,9 @@ }, "insertNulls": false, "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, "lineWidth": 1, "pointSize": 5, "scaleDistribution": { @@ -472,19 +476,22 @@ "mode": "off" } }, - "mappings": [] + "fieldMinMax": false, + "mappings": [], + "noValue": "0", + "unit": "none" }, "overrides": [ { "matcher": { "id": "byName", - "options": "total" + "options": "engine success" }, "properties": [ { "id": "color", "value": { - "fixedColor": "yellow", + "fixedColor": "dark-green", "mode": "fixed" } } @@ -493,13 +500,13 @@ { "matcher": { "id": "byName", - "options": "successes" + "options": "builder success" }, "properties": [ { "id": "color", "value": { - "fixedColor": "green", + "fixedColor": "dark-green", "mode": "fixed" } } @@ -508,13 +515,13 @@ { "matcher": { "id": "byName", - "options": "success" + "options": "engine errors" }, "properties": [ { "id": "color", "value": { - "fixedColor": "green", + "fixedColor": "dark-red", "mode": "fixed" } } @@ -523,13 +530,13 @@ { "matcher": { "id": "byName", - "options": "errors" + "options": "builder errors" }, "properties": [ { "id": "color", "value": { - "fixedColor": "red", + "fixedColor": "dark-red", "mode": "fixed" } } @@ -564,10 +571,11 @@ }, "editorMode": "code", "exemplar": false, - "expr": "rate(beacon_block_production_successes_total[$rate_interval])", + "expr": "rate(beacon_block_production_successes_total{source=\"engine\"}[$rate_interval])", + "format": "time_series", "hide": false, "interval": "", - "legendFormat": "success", + "legendFormat": "engine success", "range": true, "refId": "B" }, @@ -578,11 +586,37 @@ }, "editorMode": "code", "exemplar": false, - "expr": "rate(beacon_block_production_requests_total[$rate_interval])\n-\nrate(beacon_block_production_successes_total[$rate_interval])", + "expr": "rate(beacon_block_production_requests_total{source=\"engine\"}[$rate_interval])\n-\nrate(beacon_block_production_successes_total{source=\"engine\"}[$rate_interval])", "interval": "", - "legendFormat": "errors", + "legendFormat": "engine errors", "range": true, "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "-1 * rate(beacon_block_production_successes_total{source=\"builder\"}[$rate_interval])", + "hide": false, + "instant": false, + "legendFormat": "builder success", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "-1 *\n(\n rate(beacon_block_production_requests_total{source=\"builder\"}[$rate_interval])\n -\n rate(beacon_block_production_successes_total{source=\"builder\"}[$rate_interval])\n)", + "hide": false, + "instant": false, + "legendFormat": "builder errors", + "range": true, + "refId": "D" } ], "title": "Success + Error rates", @@ -599,6 +633,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -683,6 +718,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -743,7 +779,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(lodestar_api_rest_errors_total{operationId=~\"produceBlockV2|produceBlindedBlock|publishBlock|publishBlindedBlock\"}[$rate_interval])", + "expr": "rate(lodestar_api_rest_errors_total{operationId=~\"produceBlockV3|publishBlockV2|publishBlindedBlockV2\"}[$rate_interval])", "legendFormat": "{{operationId}}", "range": true, "refId": "A" @@ -822,7 +858,8 @@ }, "showValue": "never", "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -831,7 +868,7 @@ "unit": "s" } }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "reverseYBuckets": false, "targets": [ { @@ -874,6 +911,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1028,145 +1066,159 @@ "type": "timeseries" }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "fill": 1, - "fillGradient": 1, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "points", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 3, + "scaleDistribution": { + "log": 2, + "type": "log" + }, + "showPoints": "always", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "none" + }, + "overrides": [] + }, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 25 }, - "hiddenSeries": false, "id": 378, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": false, - "linewidth": 1, - "nullPointMode": "null", "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.1.1", - "pointradius": 0.5, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, + "editorMode": "code", "exemplar": false, - "expr": "12 * rate(lodestar_api_rest_response_time_seconds_count{operationId=~\"produceBlindedBlock|publishBlindedBlock|registerValidator\"}[$rate_interval])", + "expr": "12 * rate(lodestar_builder_http_client_request_time_seconds_count[$rate_interval])", "hide": false, "interval": "", - "legendFormat": "{{operationId}}", + "legendFormat": "{{routeId}}", + "range": true, "refId": "D" } ], - "thresholds": [], - "timeRegions": [], "title": "Builder API queries / slot", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:580", - "format": "none", - "logBase": 2, - "show": true - }, - { - "$$hashKey": "object:581", - "format": "short", - "logBase": 1, - "show": true - } - ], - "yaxis": { - "align": false - } + "type": "timeseries" }, { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "opacity", + "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": [] }, - "fill": 1, - "fillGradient": 1, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 33 }, - "hiddenSeries": false, "id": 376, - "legend": { - "avg": false, - "current": false, - "hideEmpty": false, - "hideZero": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": false, - "linewidth": 1, - "nullPointMode": "null", "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "10.1.1", - "pointradius": 0.5, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -1175,45 +1227,16 @@ }, "editorMode": "code", "exemplar": false, - "expr": "rate(lodestar_api_rest_response_time_seconds_sum{operationId=~\"produceBlindedBlock|publishBlindedBlock|registerValidator\"}[$rate_interval])\n/\nrate(lodestar_api_rest_response_time_seconds_count{operationId=~\"produceBlindedBlock|publishBlindedBlock|registerValidator\"}[$rate_interval])", + "expr": "rate(lodestar_builder_http_client_request_time_seconds_sum[$rate_interval])\n/\nrate(lodestar_builder_http_client_request_time_seconds_count[$rate_interval])", "hide": false, "interval": "", - "legendFormat": "{{operationId}}", + "legendFormat": "{{routeId}}", "range": true, "refId": "B" } ], - "thresholds": [], - "timeRegions": [], - "title": "Builder API response times", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "mode": "time", - "show": true, - "values": [] - }, - "yaxes": [ - { - "$$hashKey": "object:87", - "format": "s", - "logBase": 2, - "show": true - }, - { - "$$hashKey": "object:88", - "format": "s", - "logBase": 1, - "show": false - } - ], - "yaxis": { - "align": false - } + "title": "Builder API request times", + "type": "timeseries" }, { "datasource": { @@ -1226,6 +1249,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1328,6 +1352,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1436,6 +1461,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1540,10 +1566,12 @@ "fields": "", "values": false }, + "showPercentChange": false, "text": {}, - "textMode": "auto" + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "9.3.2", + "pluginVersion": "10.4.1", "targets": [ { "exemplar": false, @@ -1567,6 +1595,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1580,6 +1609,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1683,6 +1713,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1766,6 +1797,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1903,7 +1935,8 @@ "layout": "auto" }, "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -1911,7 +1944,7 @@ "reverse": false } }, - "pluginVersion": "9.3.2", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -1981,7 +2014,8 @@ "layout": "auto" }, "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -1989,7 +2023,7 @@ "reverse": false } }, - "pluginVersion": "9.3.2", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -2018,6 +2052,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -2031,6 +2066,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -2055,7 +2091,7 @@ "h": 8, "w": 12, "x": 0, - "y": 65 + "y": 73 }, "id": 539, "options": { @@ -2100,8 +2136,7 @@ } ], "refresh": "10s", - "schemaVersion": 38, - "style": "dark", + "schemaVersion": 39, "tags": [ "lodestar" ], diff --git a/dashboards/lodestar_state_cache_regen.json b/dashboards/lodestar_state_cache_regen.json index e7f6336eaeb9..1cca1d26c561 100644 --- a/dashboards/lodestar_state_cache_regen.json +++ b/dashboards/lodestar_state_cache_regen.json @@ -425,7 +425,32 @@ }, "mappings": [] }, - "overrides": [] + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "epochs_in-memory" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] }, "gridPos": { "h": 8, @@ -1325,30 +1350,6 @@ "value": "none" } ] - }, - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "count_per_epoch" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] } ] }, @@ -1378,7 +1379,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(lodestar_cp_state_cache_state_serialize_seconds_sum[$rate_interval])\n/\nrate(lodestar_cp_state_cache_state_serialize_seconds_count[$rate_interval])", + "expr": "rate(lodestar_state_serialize_seconds_sum{source=\"persistent_checkpoints_cache_state\"}[$rate_interval])\n/\nrate(lodestar_state_serialize_seconds_count{source=\"persistent_checkpoints_cache_state\"}[$rate_interval])", "hide": false, "instant": false, "legendFormat": "serialize_duration", @@ -1475,6 +1476,30 @@ "id": "unit" } ] + }, + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "reload_duration" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] } ] }, @@ -1610,7 +1635,32 @@ }, "mappings": [] }, - "overrides": [] + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "from_memory" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] }, "gridPos": { "h": 8, @@ -1705,7 +1755,32 @@ }, "mappings": [] }, - "overrides": [] + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "state_reload_validator_serialization" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] }, "gridPos": { "h": 8, @@ -1827,7 +1902,32 @@ }, "mappings": [] }, - "overrides": [] + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "getState" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] }, "gridPos": { "h": 8, @@ -2447,28 +2547,15 @@ }, { "collapsed": false, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 82 }, - "id": 54, + "id": 60, "panels": [], - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "refId": "A" - } - ], - "title": "Regen queue", + "title": "Regen - getState", "type": "row" }, { @@ -2489,10 +2576,9 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", + "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { - "graph": false, "legend": false, "tooltip": false, "viz": false @@ -2504,7 +2590,7 @@ "scaleDistribution": { "type": "linear" }, - "showPoints": "never", + "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", @@ -2515,45 +2601,44 @@ } }, "mappings": [], - "unit": "percentunit" + "unit": "s" }, "overrides": [] }, "gridPos": { - "h": 7, + "h": 8, "w": 12, "x": 0, "y": 83 }, - "id": 42, + "id": 61, "options": { - "graph": {}, "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", - "showLegend": false + "showLegend": true }, "tooltip": { - "mode": "multi", + "mode": "single", "sort": "none" } }, - "pluginVersion": "7.4.5", "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "exemplar": false, - "expr": "rate(lodestar_regen_queue_job_time_seconds_sum[32m])", - "interval": "", - "legendFormat": "regen_queue", + "editorMode": "code", + "expr": "rate(lodestar_regen_get_state_get_seed_state_seconds_sum[$rate_interval])\n/\nrate(lodestar_regen_get_state_get_seed_state_seconds_count[$rate_interval])", + "instant": false, + "legendFormat": "{{caller}}", + "range": true, "refId": "A" } ], - "title": "Regen queue - Utilization ratio", + "title": "Get seed state duration", "type": "timeseries" }, { @@ -2574,10 +2659,9 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", + "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { - "graph": false, "legend": false, "tooltip": false, "viz": false @@ -2589,7 +2673,7 @@ "scaleDistribution": { "type": "linear" }, - "showPoints": "never", + "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", @@ -2600,44 +2684,44 @@ } }, "mappings": [], - "unit": "none" + "unit": "s" }, "overrides": [] }, "gridPos": { - "h": 7, - "w": 6, + "h": 8, + "w": 12, "x": 12, "y": 83 }, - "id": 44, + "id": 62, "options": { - "graph": {}, "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", - "showLegend": false + "showLegend": true }, "tooltip": { - "mode": "multi", + "mode": "single", "sort": "none" } }, - "pluginVersion": "7.4.5", "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "expr": "12*rate(lodestar_regen_queue_job_time_seconds_count[$rate_interval])", - "interval": "", - "legendFormat": "regen_queue", + "editorMode": "code", + "expr": "rate(lodestar_regen_get_state_load_blocks_seconds_sum[$rate_interval])\n/\nrate(lodestar_regen_get_state_load_blocks_seconds_count[$rate_interval])", + "instant": false, + "legendFormat": "{{caller}}", + "range": true, "refId": "A" } ], - "title": "Regen queue - Jobs / slot", + "title": "Load blocks duration", "type": "timeseries" }, { @@ -2658,10 +2742,9 @@ "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", - "fillOpacity": 10, - "gradientMode": "opacity", + "fillOpacity": 0, + "gradientMode": "none", "hideFrom": { - "graph": false, "legend": false, "tooltip": false, "viz": false @@ -2673,7 +2756,7 @@ "scaleDistribution": { "type": "linear" }, - "showPoints": "never", + "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", @@ -2684,15 +2767,426 @@ } }, "mappings": [], - "unit": "percentunit" + "unit": "s" }, - "overrides": [] + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "validateGossipBlock" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] }, "gridPos": { - "h": 7, + "h": 8, + "w": 12, + "x": 0, + "y": 91 + }, + "id": 63, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_regen_get_state_state_transition_seconds_sum[$rate_interval])\n/\nrate(lodestar_regen_get_state_state_transition_seconds_count[$rate_interval])", + "instant": false, + "legendFormat": "{{caller}}", + "range": true, + "refId": "A" + } + ], + "title": "State transition duration", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "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": [] + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "validateGossipBlock" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 91 + }, + "id": 64, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_regen_get_state_block_count_sum[$rate_interval])\n/\nrate(lodestar_regen_get_state_block_count_count[$rate_interval])", + "instant": false, + "legendFormat": "{{caller}}", + "range": true, + "refId": "A" + } + ], + "title": "Reprocessed block count", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 99 + }, + "id": 54, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "refId": "A" + } + ], + "title": "Regen queue", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 100 + }, + "id": 42, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": false, + "expr": "rate(lodestar_regen_queue_job_time_seconds_sum[32m])", + "interval": "", + "legendFormat": "regen_queue", + "refId": "A" + } + ], + "title": "Regen queue - Utilization ratio", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 100 + }, + "id": 44, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "12*rate(lodestar_regen_queue_job_time_seconds_count[$rate_interval])", + "interval": "", + "legendFormat": "regen_queue", + "refId": "A" + } + ], + "title": "Regen queue - Jobs / slot", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, "w": 6, "x": 18, - "y": 83 + "y": 100 }, "id": 48, "options": { @@ -2776,7 +3270,7 @@ "h": 7, "w": 12, "x": 0, - "y": 90 + "y": 107 }, "id": 46, "options": { @@ -2860,7 +3354,7 @@ "h": 7, "w": 6, "x": 12, - "y": 90 + "y": 107 }, "id": 50, "options": { @@ -2944,7 +3438,7 @@ "h": 7, "w": 6, "x": 18, - "y": 90 + "y": 107 }, "id": 52, "options": { diff --git a/dashboards/lodestar_validator_client.json b/dashboards/lodestar_validator_client.json index 5e4459d1d1b9..f954f0a04ccd 100644 --- a/dashboards/lodestar_validator_client.json +++ b/dashboards/lodestar_validator_client.json @@ -497,7 +497,7 @@ }, "gridPos": { "h": 3, - "w": 6, + "w": 3, "x": 12, "y": 2 }, @@ -552,8 +552,8 @@ }, "gridPos": { "h": 3, - "w": 6, - "x": 18, + "w": 3, + "x": 15, "y": 2 }, "id": 37, @@ -590,6 +590,91 @@ "title": "Heap used", "type": "stat" }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "cellOptions": { + "type": "color-text" + }, + "inspect": false + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 2 + }, + "id": 48, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "vc_default_configuration", + "format": "table", + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A" + } + ], + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Value": true, + "__name__": true, + "client_name": true, + "group": true, + "host_type": true, + "instance": true, + "job": true, + "network": true, + "scrape_location": true + }, + "includeByName": {}, + "indexByName": {}, + "renameByName": { + "broadcastValidation": "Broadcast validation", + "builderSelection": "Builder selection" + } + } + } + ], + "type": "table" + }, { "datasource": { "type": "prometheus", diff --git a/docs/yarn.lock b/docs/yarn.lock index 41966c013183..046829e3530b 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2430,22 +2430,6 @@ dependencies: "@types/ms" "*" -"@types/eslint-scope@^3.7.3": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.56.5" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.5.tgz#94b88cab77588fcecdd0771a6d576fa1c0af9d02" - integrity sha512-u5/YPJHo1tvkSF2CE0USEkxon82Z5DBy2xR+qfyYNszpX9qcs4sT6uq2kBbj4BXY1+DBGDPnrhMZV3pKWGNukw== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - "@types/estree-jsx@^1.0.0": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" @@ -2536,7 +2520,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -2736,10 +2720,10 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24" - integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q== +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== dependencies: "@webassemblyjs/helper-numbers" "1.11.6" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" @@ -2754,10 +2738,10 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== -"@webassemblyjs/helper-buffer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093" - integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA== +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== "@webassemblyjs/helper-numbers@1.11.6": version "1.11.6" @@ -2773,15 +2757,15 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577" - integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g== +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" "@webassemblyjs/ieee754@1.11.6": version "1.11.6" @@ -2802,59 +2786,59 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab" - integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw== +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" - "@webassemblyjs/helper-wasm-section" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-opt" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" - "@webassemblyjs/wast-printer" "1.11.6" - -"@webassemblyjs/wasm-gen@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268" - integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA== - dependencies: - "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" "@webassemblyjs/ieee754" "1.11.6" "@webassemblyjs/leb128" "1.11.6" "@webassemblyjs/utf8" "1.11.6" -"@webassemblyjs/wasm-opt@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2" - integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g== +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== dependencies: - "@webassemblyjs/ast" "1.11.6" - "@webassemblyjs/helper-buffer" "1.11.6" - "@webassemblyjs/wasm-gen" "1.11.6" - "@webassemblyjs/wasm-parser" "1.11.6" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" -"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1" - integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ== +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== dependencies: - "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/ast" "1.12.1" "@webassemblyjs/helper-api-error" "1.11.6" "@webassemblyjs/helper-wasm-bytecode" "1.11.6" "@webassemblyjs/ieee754" "1.11.6" "@webassemblyjs/leb128" "1.11.6" "@webassemblyjs/utf8" "1.11.6" -"@webassemblyjs/wast-printer@1.11.6": - version "1.11.6" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20" - integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A== +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== dependencies: - "@webassemblyjs/ast" "1.11.6" + "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" "@xtuc/ieee754@^1.2.0": @@ -2875,10 +2859,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn-jsx@^5.0.0: version "5.3.2" @@ -3136,10 +3120,10 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -3149,7 +3133,7 @@ body-parser@1.20.2: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" + qs "6.13.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -3203,7 +3187,14 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -4361,10 +4352,10 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" -dompurify@^3.0.5: - version "3.0.9" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.9.tgz#b3f362f24b99f53498c75d43ecbd784b0b3ad65e" - integrity sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ== +"dompurify@^3.0.5 <3.1.7": + version "3.1.6" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.6.tgz#43c714a94c6a7b8801850f82e756685300a027e2" + integrity sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ== domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" @@ -4454,10 +4445,15 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -enhanced-resolve@^5.15.0: - version "5.15.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.1.tgz#384391e025f099e67b4b00bfd7f0906a408214e1" - integrity sha512-3d3JRbwsCLJsYgvb6NuWEG44jjPSOMuS73L/6+7BZuoKm3W+qXnSoIYVHi8dG7Qcg4inAY4jbzkZ7MnskePeDg== +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +enhanced-resolve@^5.17.1: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -4659,36 +4655,36 @@ execa@^5.0.0: strip-final-newline "^2.0.0" express@^4.17.3: - version "4.19.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + version "4.21.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" + integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.2" + body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.2.0" + finalhandler "1.3.1" fresh "0.5.2" http-errors "2.0.0" - merge-descriptors "1.0.1" + merge-descriptors "1.0.3" methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.7" + path-to-regexp "0.1.10" proxy-addr "~2.0.7" - qs "6.11.0" + qs "6.13.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" + send "0.19.0" + serve-static "1.16.2" setprototypeof "1.2.0" statuses "2.0.1" type-is "~1.6.18" @@ -4783,13 +4779,20 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== dependencies: debug "2.6.9" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" on-finished "2.4.1" parseurl "~1.3.3" @@ -5071,7 +5074,7 @@ graceful-fs@4.2.10: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -6345,10 +6348,10 @@ memfs@^3.1.2, memfs@^3.4.3: dependencies: fs-monkey "^1.0.4" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== merge-stream@^2.0.0: version "2.0.0" @@ -6361,9 +6364,9 @@ merge2@^1.3.0, merge2@^1.4.1: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== mermaid@^10.4.0: - version "10.9.0" - resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.9.0.tgz#4d1272fbe434bd8f3c2c150554dc8a23a9bf9361" - integrity sha512-swZju0hFox/B/qoLKK0rOxxgh8Cf7rJSfAUc1u8fezVihYMvrJAS45GzAxTVf4Q+xn9uMgitBcmWk7nWGXOs/g== + version "10.9.3" + resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.9.3.tgz#90bc6f15c33dbe5d9507fed31592cc0d88fee9f7" + integrity sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw== dependencies: "@braintree/sanitize-url" "^6.0.1" "@types/d3-scale" "^4.0.3" @@ -6374,7 +6377,7 @@ mermaid@^10.4.0: d3-sankey "^0.12.3" dagre-d3-es "7.0.10" dayjs "^1.11.7" - dompurify "^3.0.5" + dompurify "^3.0.5 <3.1.7" elkjs "^0.9.0" katex "^0.16.9" khroma "^2.0.0" @@ -6976,11 +6979,11 @@ micromark@^4.0.0: micromark-util-types "^2.0.0" micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": @@ -7426,10 +7429,10 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== path-to-regexp@2.2.1: version "2.2.1" @@ -7854,12 +7857,12 @@ pupa@^3.1.0: dependencies: escape-goat "^4.0.0" -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== dependencies: - side-channel "^1.0.4" + side-channel "^1.0.6" queue-microtask@^1.2.2: version "1.2.3" @@ -8458,10 +8461,10 @@ semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: dependencies: lru-cache "^6.0.0" -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== dependencies: debug "2.6.9" depd "2.0.0" @@ -8511,15 +8514,15 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== dependencies: - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" parseurl "~1.3.3" - send "0.18.0" + send "0.19.0" set-function-length@^1.2.1: version "1.2.1" @@ -8581,7 +8584,7 @@ shelljs@^0.8.5: interpret "^1.0.0" rechoir "^0.6.2" -side-channel@^1.0.4: +side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== @@ -9246,10 +9249,10 @@ vfile@^6.0.0, vfile@^6.0.1: unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -9352,25 +9355,24 @@ webpack-sources@^3.2.2, webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.88.1: - version "5.90.3" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.90.3.tgz#37b8f74d3ded061ba789bb22b31e82eed75bd9ac" - integrity sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA== + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== dependencies: - "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" + acorn-import-attributes "^1.9.5" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" + enhanced-resolve "^5.17.1" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -9378,7 +9380,7 @@ webpack@^5.88.1: schema-utils "^3.2.0" tapable "^2.1.1" terser-webpack-plugin "^5.3.10" - watchpack "^2.4.0" + watchpack "^2.4.1" webpack-sources "^3.2.3" webpackbar@^5.0.2: diff --git a/funding.json b/funding.json index 872321cdca5b..3ff79f54b693 100644 --- a/funding.json +++ b/funding.json @@ -1,5 +1,10 @@ { "opRetro": { "projectId": "0x8ec88058175ef4c1c9b1f26910c4d4f2cfa733d6fcd1dbd9385476a313d9e12d" + }, + "drips": { + "ethereum": { + "ownedBy": "0x94107e24Ba695aeb884fe9e896BA0Bbc14D3B509" + } } } diff --git a/lerna.json b/lerna.json index 0ffc65fe5402..6d76d7f4a488 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useNx": true, - "version": "1.22.0", + "version": "1.23.0", "stream": true, "command": { "version": { diff --git a/package.json b/package.json index 85d9662ab920..7399b4ba6c1e 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "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": "biome check", + "lint:fix": "yarn lint --write", "lint-dashboards": "node scripts/lint-grafana-dashboards.mjs ./dashboards", "check-build": "lerna run check-build", "check-bundle": "lerna run check-bundle", @@ -48,22 +48,15 @@ }, "devDependencies": { "@actions/core": "^1.10.1", - "@chainsafe/eslint-plugin-node": "^11.2.3", "@dapplion/benchmark": "^0.2.4", + "@biomejs/biome": "^1.9.3", "@types/mocha": "^10.0.6", "@types/node": "^20.12.8", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", "@vitest/browser": "^2.0.4", "@vitest/coverage-v8": "^2.0.4", "crypto-browserify": "^3.12.0", "dotenv": "^16.4.5", "electron": "^26.2.2", - "eslint": "^8.57.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-vitest": "^0.3.26", "https-browserify": "^1.0.0", "jsdom": "^23.0.1", "lerna": "^7.3.0", diff --git a/packages/api/package.json b/packages/api/package.json index d2f8a621f02e..a787147f6f07 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.22.0", + "version": "1.23.0", "type": "module", "exports": { ".": { @@ -63,19 +63,19 @@ "build:release": "yarn clean && yarn run build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit", "test:unit": "vitest --run --dir test/unit/", "check-readme": "typescript-docs-verifier" }, "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.8.0", - "@chainsafe/ssz": "^0.17.1", - "@lodestar/config": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/types": "^1.22.0", - "@lodestar/utils": "^1.22.0", + "@chainsafe/ssz": "^0.18.0", + "@lodestar/config": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0", "eventsource": "^2.0.2", "qs": "^6.11.1" }, @@ -83,7 +83,7 @@ "@types/eventsource": "^1.1.11", "@types/qs": "^6.9.7", "ajv": "^8.12.0", - "fastify": "^4.27.0" + "fastify": "^5.0.0" }, "keywords": [ "ethereum", diff --git a/packages/api/src/beacon/client/events.ts b/packages/api/src/beacon/client/events.ts index 34f14f2e8397..9937b850b49a 100644 --- a/packages/api/src/beacon/client/events.ts +++ b/packages/api/src/beacon/client/events.ts @@ -16,10 +16,15 @@ export function getClient(config: ChainForkConfig, baseUrl: string): ApiClient { const eventSerdes = getEventSerdes(config); return { - eventstream: async ({topics, signal, onEvent, onError, onClose}) => { + eventstream: async ({ + topics, + signal, + onEvent, + onError, + onClose, + }): Promise> => { const query = stringifyQuery({topics}); 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); @@ -40,7 +45,7 @@ export function getClient(config: ChainForkConfig, baseUrl: string): ApiClient { // 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) { + eventSource.onerror = function onerror(err): void { const errEs = err as unknown as EventSourceError; // Ignore noisy errors due to beacon node being offline diff --git a/packages/api/src/beacon/routes/beacon/block.ts b/packages/api/src/beacon/routes/beacon/block.ts index be6789753e0f..cd3cae9fd7ff 100644 --- a/packages/api/src/beacon/routes/beacon/block.ts +++ b/packages/api/src/beacon/routes/beacon/block.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {ContainerType, ListCompositeType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import { @@ -225,7 +224,7 @@ export type Endpoints = { >; }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: const blockIdOnlyReq: RequestCodec> = { writeReq: ({blockId}) => ({params: {block_id: blockId.toString()}}), parseReq: ({params}) => ({blockId: params.block_id}), @@ -500,7 +499,6 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions(), validator: ssz.phase0.Validator, }); +export const ValidatorIdentityType = new ContainerType( + { + index: ssz.ValidatorIndex, + pubkey: ssz.BLSPubkey, + activationEpoch: ssz.UintNum64, + }, + {jsonCase: "eth2"} +); export const EpochCommitteeResponseType = new ContainerType({ index: ssz.CommitteeIndex, slot: ssz.Slot, @@ -73,6 +70,7 @@ export const EpochSyncCommitteeResponseType = new ContainerType( {jsonCase: "eth2"} ); export const ValidatorResponseListType = ArrayOf(ValidatorResponseType); +export const ValidatorIdentitiesType = ArrayOf(ValidatorIdentityType); export const EpochCommitteeResponseListType = ArrayOf(EpochCommitteeResponseType); export const ValidatorBalanceListType = ArrayOf(ValidatorBalanceType); @@ -84,6 +82,7 @@ export type ValidatorBalance = ValueOf; export type EpochSyncCommitteeResponse = ValueOf; export type ValidatorResponseList = ValueOf; +export type ValidatorIdentities = ValueOf; export type EpochCommitteeResponseList = ValueOf; export type ValidatorBalanceList = ValueOf; @@ -191,6 +190,26 @@ export type Endpoints = { ExecutionOptimisticAndFinalizedMeta >; + /** + * Get validator identities from state + * + * Returns filterable list of validators identities. + * + * Identities will be returned for all indices or public keys that match known validators. If an index or public key does not + * match any known validator, no identity will be returned but this will not cause an error. There are no guarantees for the + * returned data in terms of ordering. + */ + postStateValidatorIdentities: Endpoint< + "POST", + StateArgs & { + /** An array of values, with each value either a hex encoded public key (any bytes48 with 0x prefix) or a validator index */ + validatorIds?: ValidatorId[]; + }, + {params: {state_id: string}; body: string[]}, + ValidatorIdentities, + ExecutionOptimisticAndFinalizedMeta + >; + /** * Get validator balances from state * Returns filterable list of validator balances. @@ -249,7 +268,7 @@ export type Endpoints = { >; }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: const stateIdOnlyReq: RequestCodec> = { writeReq: ({stateId}) => ({params: {state_id: stateId.toString()}}), parseReq: ({params}) => ({stateId: params.state_id}), @@ -404,6 +423,28 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({ + 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, + }, + }), + resp: { + data: ValidatorIdentitiesType, + meta: ExecutionOptimisticAndFinalizedCodec, + }, + }, getStateValidatorBalances: { url: "/eth/v1/beacon/states/{state_id}/validator_balances", method: "GET", diff --git a/packages/api/src/beacon/routes/config.ts b/packages/api/src/beacon/routes/config.ts index f8a606af1431..afce0898908c 100644 --- a/packages/api/src/beacon/routes/config.ts +++ b/packages/api/src/beacon/routes/config.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {ContainerType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import {ssz} from "@lodestar/types"; diff --git a/packages/api/src/beacon/routes/debug.ts b/packages/api/src/beacon/routes/debug.ts index 8099fcac020e..349684f62ccd 100644 --- a/packages/api/src/beacon/routes/debug.ts +++ b/packages/api/src/beacon/routes/debug.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {ContainerType, Type, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import {ssz, StringType, BeaconState} from "@lodestar/types"; @@ -58,11 +57,34 @@ const DebugChainHeadType = new ContainerType( {jsonCase: "eth2"} ); +const ForkChoiceNodeType = new ContainerType( + { + slot: ssz.Slot, + blockRoot: stringType, + parentRoot: stringType, + justifiedEpoch: ssz.Epoch, + finalizedEpoch: ssz.Epoch, + weight: ssz.UintNum64, + validity: new StringType<"valid" | "invalid" | "optimistic">(), + executionBlockHash: stringType, + }, + {jsonCase: "eth2"} +); +const ForkChoiceResponseType = new ContainerType( + { + justifiedCheckpoint: ssz.phase0.Checkpoint, + finalizedCheckpoint: ssz.phase0.Checkpoint, + forkChoiceNodes: ArrayOf(ForkChoiceNodeType), + }, + {jsonCase: "eth2"} +); + const ProtoNodeListType = ArrayOf(ProtoNodeType); const DebugChainHeadListType = ArrayOf(DebugChainHeadType); type ProtoNodeList = ValueOf; type DebugChainHeadList = ValueOf; +type ForkChoiceResponse = ValueOf; export type Endpoints = { /** @@ -77,6 +99,18 @@ export type Endpoints = { EmptyMeta >; + /** + * Retrieves all current fork choice context + */ + getDebugForkChoice: Endpoint< + // ⏎ + "GET", + EmptyArgs, + EmptyRequest, + ForkChoiceResponse, + EmptyMeta + >; + /** * Dump all ProtoArray's nodes to debug */ @@ -115,6 +149,24 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({ + ...(data as ForkChoiceResponse), + }), + fromResponse: (resp) => ({ + data: resp as ForkChoiceResponse, + }), + }, + }, + }, getProtoArrayNodes: { url: "/eth/v0/debug/forkchoice", method: "GET", diff --git a/packages/api/src/beacon/routes/events.ts b/packages/api/src/beacon/routes/events.ts index 1f041aa30194..5fa218bc02f3 100644 --- a/packages/api/src/beacon/routes/events.ts +++ b/packages/api/src/beacon/routes/events.ts @@ -188,7 +188,6 @@ export type TypeJson = { }; export function getTypeByEvent(config: ChainForkConfig): {[K in EventType]: TypeJson} { - // eslint-disable-next-line @typescript-eslint/naming-convention const WithVersion = (getType: (fork: ForkName) => TypeJson): TypeJson<{data: T; version: ForkName}> => { return { toJson: ({data, version}) => ({ @@ -289,7 +288,6 @@ export function getTypeByEvent(config: ChainForkConfig): {[K in EventType]: Type }; } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function getEventSerdes(config: ChainForkConfig) { const typeByEvent = getTypeByEvent(config); diff --git a/packages/api/src/beacon/routes/lightclient.ts b/packages/api/src/beacon/routes/lightclient.ts index ab45323f8f64..bac73b43ddb2 100644 --- a/packages/api/src/beacon/routes/lightclient.ts +++ b/packages/api/src/beacon/routes/lightclient.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {ListCompositeType, ValueOf} from "@chainsafe/ssz"; import { LightClientBootstrap, @@ -8,10 +7,12 @@ import { ssz, SyncPeriod, } from "@lodestar/types"; -import {ForkName} from "@lodestar/params"; -import {ChainForkConfig} from "@lodestar/config"; +import {fromHex} from "@lodestar/utils"; +import {ForkName, ZERO_HASH} from "@lodestar/params"; +import {BeaconConfig, ChainForkConfig, createBeaconConfig} from "@lodestar/config"; +import {genesisData, NetworkName} from "@lodestar/config/networks"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; -import {VersionCodec, VersionMeta} from "../../utils/metadata.js"; +import {MetaHeader, VersionCodec, VersionMeta} from "../../utils/metadata.js"; import {getLightClientForkTypes, toForkName} from "../../utils/fork.js"; import { EmptyArgs, @@ -20,7 +21,6 @@ import { EmptyMetaCodec, EmptyRequest, WithVersion, - JsonOnlyResp, } from "../../utils/codecs.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -91,7 +91,18 @@ export type Endpoints = { >; }; -export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { +export function getDefinitions(config: ChainForkConfig): RouteDefinitions { + // Cache config so fork digests don't need to be recomputed + let beaconConfig: BeaconConfig | undefined; + + const cachedBeaconConfig = (): BeaconConfig => { + if (beaconConfig === undefined) { + const genesisValidatorsRoot = genesisData[config.CONFIG_NAME as NetworkName]?.genesisValidatorsRoot; + beaconConfig = createBeaconConfig(config, genesisValidatorsRoot ? fromHex(genesisValidatorsRoot) : ZERO_HASH); + } + return beaconConfig; + }; + return { getLightClientUpdatesByRange: { url: "/eth/v1/beacon/light_client/updates", @@ -101,7 +112,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({startPeriod: query.start_period, count: query.count}), schema: {query: {start_period: Schema.UintRequired, count: Schema.UintRequired}}, }, - resp: JsonOnlyResp({ + resp: { data: { toJson: (data, meta) => { const json: unknown[] = []; @@ -119,12 +130,44 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions { + const chunks: Uint8Array[] = []; + for (const [i, update] of data.entries()) { + const version = meta.versions[i]; + const forkDigest = cachedBeaconConfig().forkName2ForkDigest(version); + const serialized = getLightClientForkTypes(version).LightClientUpdate.serialize(update); + const length = ssz.UintNum64.serialize(4 + serialized.length); + chunks.push(length, forkDigest, serialized); + } + return Buffer.concat(chunks); + }, + deserialize: (data) => { + let offset = 0; + const updates: LightClientUpdate[] = []; + while (offset < data.length) { + const length = ssz.UintNum64.deserialize(data.subarray(offset, offset + 8)); + const forkDigest = ssz.ForkDigest.deserialize(data.subarray(offset + 8, offset + 12)); + const version = cachedBeaconConfig().forkDigest2ForkName(forkDigest); + updates.push( + getLightClientForkTypes(version).LightClientUpdate.deserialize( + data.subarray(offset + 12, offset + 8 + length) + ) + ); + offset += 8 + length; + } + return updates; + }, }, meta: { toJson: (meta) => meta, fromJson: (val) => val as {versions: ForkName[]}, - toHeadersObject: () => ({}), - fromHeaders: () => ({versions: []}), + toHeadersObject: (meta) => ({ + [MetaHeader.Version]: meta.versions.join(","), + }), + fromHeaders: (headers) => { + const versions = headers.getOrDefault(MetaHeader.Version, ""); + return {versions: versions === "" ? [] : (versions.split(",") as ForkName[])}; + }, }, transform: { toResponse: (data, meta) => { @@ -148,7 +191,7 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions batches: any[]; }; diff --git a/packages/api/src/beacon/routes/node.ts b/packages/api/src/beacon/routes/node.ts index 0744b5f07452..c7a6c2e36361 100644 --- a/packages/api/src/beacon/routes/node.ts +++ b/packages/api/src/beacon/routes/node.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {ContainerType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import {ssz, stringType} from "@lodestar/types"; diff --git a/packages/api/src/beacon/routes/proof.ts b/packages/api/src/beacon/routes/proof.ts index 5c20a0194fc5..0c6d5a058cea 100644 --- a/packages/api/src/beacon/routes/proof.ts +++ b/packages/api/src/beacon/routes/proof.ts @@ -1,6 +1,6 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {CompactMultiProof, ProofType} from "@chainsafe/persistent-merkle-tree"; -import {ByteListType, ContainerType, fromHexString, toHexString} from "@chainsafe/ssz"; +import {ByteListType, ContainerType} from "@chainsafe/ssz"; +import {fromHex, toHex} from "@lodestar/utils"; import {ChainForkConfig} from "@lodestar/config"; import {ssz} from "@lodestar/types"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; @@ -45,8 +45,8 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId}, query: {format: toHexString(descriptor)}}), - parseReq: ({params, query}) => ({stateId: params.state_id, descriptor: fromHexString(query.format)}), + writeReq: ({stateId, descriptor}) => ({params: {state_id: stateId}, query: {format: toHex(descriptor)}}), + parseReq: ({params, query}) => ({stateId: params.state_id, descriptor: fromHex(query.format)}), schema: {params: {state_id: Schema.StringRequired}, query: {format: Schema.StringRequired}}, }, resp: { @@ -63,8 +63,8 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {block_id: blockId}, query: {format: toHexString(descriptor)}}), - parseReq: ({params, query}) => ({blockId: params.block_id, descriptor: fromHexString(query.format)}), + writeReq: ({blockId, descriptor}) => ({params: {block_id: blockId}, query: {format: toHex(descriptor)}}), + parseReq: ({params, query}) => ({blockId: params.block_id, descriptor: fromHex(query.format)}), schema: {params: {block_id: Schema.StringRequired}, query: {format: Schema.StringRequired}}, }, resp: { diff --git a/packages/api/src/beacon/routes/validator.ts b/packages/api/src/beacon/routes/validator.ts index 664caf44a2c4..cf30d5748a46 100644 --- a/packages/api/src/beacon/routes/validator.ts +++ b/packages/api/src/beacon/routes/validator.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {ContainerType, fromHexString, toHexString, Type, ValueOf} from "@chainsafe/ssz"; +import {ContainerType, Type, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; import {isForkBlobs, isForkPostElectra} from "@lodestar/params"; import { @@ -20,6 +19,7 @@ import { Attestation, sszTypesFor, } from "@lodestar/types"; +import {fromHex, toHex, toRootHex} from "@lodestar/utils"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; import {fromGraffitiHex, toBoolean, toGraffitiHex} from "../../utils/serdes.js"; import {getExecutionForkTypes, toForkName} from "../../utils/fork.js"; @@ -623,7 +623,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({ params: {slot}, query: { - randao_reveal: toHexString(randaoReveal), + randao_reveal: toHex(randaoReveal), graffiti: toGraffitiHex(graffiti), fee_recipient: feeRecipient, builder_selection: builderSelection, @@ -632,7 +632,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({ slot: params.slot, - randaoReveal: fromHexString(query.randao_reveal), + randaoReveal: fromHex(query.randao_reveal), graffiti: fromGraffitiHex(query.graffiti), feeRecipient: query.fee_recipient, builderSelection: query.builder_selection as BuilderSelection, @@ -674,7 +674,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({ params: {slot}, query: { - randao_reveal: toHexString(randaoReveal), + randao_reveal: toHex(randaoReveal), graffiti: toGraffitiHex(graffiti), skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification), fee_recipient: feeRecipient, @@ -686,7 +686,7 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({ slot: params.slot, - randaoReveal: fromHexString(query.randao_reveal), + randaoReveal: fromHex(query.randao_reveal), graffiti: fromGraffitiHex(query.graffiti), skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification), feeRecipient: query.fee_recipient, @@ -765,11 +765,11 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({ params: {slot}, - query: {randao_reveal: toHexString(randaoReveal), graffiti: toGraffitiHex(graffiti)}, + query: {randao_reveal: toHex(randaoReveal), graffiti: toGraffitiHex(graffiti)}, }), parseReq: ({params, query}) => ({ slot: params.slot, - randaoReveal: fromHexString(query.randao_reveal), + randaoReveal: fromHex(query.randao_reveal), graffiti: fromGraffitiHex(query.graffiti), }), schema: { @@ -805,12 +805,12 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({ - query: {slot, subcommittee_index: subcommitteeIndex, beacon_block_root: toHexString(beaconBlockRoot)}, + query: {slot, subcommittee_index: subcommitteeIndex, beacon_block_root: toRootHex(beaconBlockRoot)}, }), parseReq: ({query}) => ({ slot: query.slot, subcommitteeIndex: query.subcommittee_index, - beaconBlockRoot: fromHexString(query.beacon_block_root), + beaconBlockRoot: fromHex(query.beacon_block_root), }), schema: { query: { @@ -830,10 +830,10 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({ - query: {attestation_data_root: toHexString(attestationDataRoot), slot}, + query: {attestation_data_root: toRootHex(attestationDataRoot), slot}, }), parseReq: ({query}) => ({ - attestationDataRoot: fromHexString(query.attestation_data_root), + attestationDataRoot: fromHex(query.attestation_data_root), slot: query.slot, }), schema: { @@ -853,10 +853,10 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({ - query: {attestation_data_root: toHexString(attestationDataRoot), slot, committee_index: committeeIndex}, + query: {attestation_data_root: toHex(attestationDataRoot), slot, committee_index: committeeIndex}, }), parseReq: ({query}) => ({ - attestationDataRoot: fromHexString(query.attestation_data_root), + attestationDataRoot: fromHex(query.attestation_data_root), slot: query.slot, committeeIndex: query.committee_index, }), diff --git a/packages/api/src/beacon/server/events.ts b/packages/api/src/beacon/server/events.ts index 96212f006d8f..b9027ab1879d 100644 --- a/packages/api/src/beacon/server/events.ts +++ b/packages/api/src/beacon/server/events.ts @@ -23,9 +23,9 @@ export function getRoutes(config: ChainForkConfig, methods: ApplicationMethods { + for (const [key, value] of Object.entries(res.getHeaders())) { if (value !== undefined) res.raw.setHeader(key, value); - }); + } res.raw.setHeader("Content-Type", "text/event-stream"); res.raw.setHeader("Cache-Control", "no-cache,no-transform"); diff --git a/packages/api/src/beacon/server/index.ts b/packages/api/src/beacon/server/index.ts index 21c0607d3f14..304f4d42be17 100644 --- a/packages/api/src/beacon/server/index.ts +++ b/packages/api/src/beacon/server/index.ts @@ -25,7 +25,7 @@ export function registerRoutes( // 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 + // biome-ignore lint/suspicious/noExplicitAny: [K2 in keyof Endpoints[K]]: FastifyRoute; }; } = { diff --git a/packages/api/src/builder/routes.ts b/packages/api/src/builder/routes.ts index 3d74101bb046..3911a515e1c6 100644 --- a/packages/api/src/builder/routes.ts +++ b/packages/api/src/builder/routes.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString, toHexString} from "@chainsafe/ssz"; import { ssz, bellatrix, @@ -10,10 +8,11 @@ import { ExecutionPayloadAndBlobsBundle, SignedBlindedBeaconBlock, SignedBuilderBid, + WithOptionalBytes, } from "@lodestar/types"; import {ForkName, isForkBlobs} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; -import {toPubkeyHex} from "@lodestar/utils"; +import {fromHex, toPubkeyHex, toRootHex} from "@lodestar/utils"; import {Endpoint, RouteDefinitions, Schema} from "../utils/index.js"; import {MetaHeader, VersionCodec, VersionMeta} from "../utils/metadata.js"; @@ -73,16 +72,13 @@ export type Endpoints = { submitBlindedBlock: Endpoint< "POST", - {signedBlindedBlock: SignedBlindedBeaconBlock}, + {signedBlindedBlock: WithOptionalBytes}, {body: unknown; headers: {[MetaHeader.Version]: string}}, ExecutionPayload | ExecutionPayloadAndBlobsBundle, VersionMeta >; }; -// 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: { @@ -106,12 +102,12 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions ({ - params: {slot, parent_hash: toHexString(parentHash), pubkey: toPubkeyHex(proposerPubKey)}, + params: {slot, parent_hash: toRootHex(parentHash), pubkey: toPubkeyHex(proposerPubKey)}, }), parseReq: ({params}) => ({ slot: params.slot, - parentHash: fromHexString(params.parent_hash), - proposerPubkey: fromHexString(params.pubkey), + parentHash: fromHex(params.parent_hash), + proposerPubkey: fromHex(params.pubkey), }), schema: { params: {slot: Schema.UintRequired, parent_hash: Schema.StringRequired, pubkey: Schema.StringRequired}, @@ -127,11 +123,11 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { - const fork = config.getForkName(signedBlindedBlock.message.slot); + const fork = config.getForkName(signedBlindedBlock.data.message.slot); return { - body: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.toJson(signedBlindedBlock), + body: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.toJson(signedBlindedBlock.data), headers: { [MetaHeader.Version]: fork, }, @@ -140,14 +136,31 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); return { - signedBlindedBlock: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.fromJson(body), + signedBlindedBlock: {data: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.fromJson(body)}, + }; + }, + writeReqSsz: ({signedBlindedBlock}) => { + const fork = config.getForkName(signedBlindedBlock.data.message.slot); + return { + body: + signedBlindedBlock.bytes ?? + getExecutionForkTypes(fork).SignedBlindedBeaconBlock.serialize(signedBlindedBlock.data), + headers: { + [MetaHeader.Version]: fork, + }, + }; + }, + parseReqSsz: ({body, headers}) => { + const fork = toForkName(fromHeaders(headers, MetaHeader.Version)); + return { + signedBlindedBlock: {data: getExecutionForkTypes(fork).SignedBlindedBeaconBlock.deserialize(body)}, }; }, schema: { body: Schema.Object, headers: {[MetaHeader.Version]: Schema.String}, }, - }), + }, resp: { data: WithVersion((fork: ForkName) => { return isForkBlobs(fork) diff --git a/packages/api/src/keymanager/routes.ts b/packages/api/src/keymanager/routes.ts index b6abe8928d77..0db096d14e83 100644 --- a/packages/api/src/keymanager/routes.ts +++ b/packages/api/src/keymanager/routes.ts @@ -1,4 +1,3 @@ -/* 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"; diff --git a/packages/api/src/utils/client/eventSource.ts b/packages/api/src/utils/client/eventSource.ts index 6b5b75124034..f023a0ec5f0b 100644 --- a/packages/api/src/utils/client/eventSource.ts +++ b/packages/api/src/utils/client/eventSource.ts @@ -1,9 +1,7 @@ // This function switches between the native web implementation and a nodejs implemnetation export async function getEventSource(): Promise { - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (globalThis.EventSource) { return EventSource; - } else { - return (await import("eventsource")).default as unknown as typeof EventSource; } + return (await import("eventsource")).default as unknown as typeof EventSource; } diff --git a/packages/api/src/utils/client/fetch.ts b/packages/api/src/utils/client/fetch.ts index a338d82e521f..31e52cdbd67f 100644 --- a/packages/api/src/utils/client/fetch.ts +++ b/packages/api/src/utils/client/fetch.ts @@ -6,7 +6,7 @@ async function wrappedFetch(url: string | URL, init?: RequestInit): Promise { try { // This function wraps global `fetch` which should only be directly called here - // eslint-disable-next-line no-restricted-globals + // biome-ignore lint/style/noRestrictedGlobals: return await fetch(url, init); } catch (e) { throw new FetchError(url, e); diff --git a/packages/api/src/utils/client/httpClient.ts b/packages/api/src/utils/client/httpClient.ts index e11e35dc85c7..33b93e3a9d41 100644 --- a/packages/api/src/utils/client/httpClient.ts +++ b/packages/api/src/utils/client/httpClient.ts @@ -155,12 +155,10 @@ export class HttpClient implements IHttpClient { if (init.retries > 0) { return this.requestWithRetries(definition, args, init); - } else { - return this.getRequestMethod(init)(definition, args, init); } - } else { - return this.requestWithFallbacks(definition, args, localInit); + return this.getRequestMethod(init)(definition, args, init); } + return this.requestWithFallbacks(definition, args, localInit); } /** @@ -198,8 +196,8 @@ export class HttpClient implements IHttpClient { this.logger?.debug("Requesting fallback URL", {routeId, baseUrl: printableUrl, score: this.urlsScore[i]}); } - // eslint-disable-next-line @typescript-eslint/naming-convention - const i_ = i; // Keep local copy of i variable to index urlScore after requestMethod() resolves + // biome-ignore lint/style/useNamingConvention: Author preferred this format + const i_ = i; // Keep local copy of i variable to index urlScore after requestWithBody() resolves const urlInit = this.urlsInits[i]; if (urlInit === undefined) { @@ -253,19 +251,16 @@ 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); - } } + if (i >= this.urlsInits.length - 1) { + return res; + } + this.logger?.debug("Request error, retrying", {}, res.error() as Error); } catch (e) { if (i >= this.urlsInits.length - 1) { throw e; - } else { - this.logger?.debug("Request error, retrying", {}, e as Error); } + this.logger?.debug("Request error, retrying", {}, e as Error); } } @@ -353,7 +348,9 @@ export class HttpClient implements IHttpClient { // Attach global/local signal to this request's controller const onSignalAbort = (): void => controller.abort(); - abortSignals.forEach((s) => s?.addEventListener("abort", onSignalAbort)); + for (const s of abortSignals) { + s?.addEventListener("abort", onSignalAbort); + } const routeId = definition.operationId; const {printableUrl, requestWireFormat, responseWireFormat} = init; @@ -390,19 +387,20 @@ export class HttpClient implements IHttpClient { if (isAbortedError(e)) { if (abortSignals.some((s) => s?.aborted)) { throw new ErrorAborted(`${routeId} request`); - } else if (controller.signal.aborted) { + } + if (controller.signal.aborted) { throw new TimeoutError(`${routeId} request`); - } else { - throw Error("Unknown aborted error"); } - } else { - throw e; + throw Error("Unknown aborted error"); } + throw e; } finally { timer?.(); clearTimeout(timeout); - abortSignals.forEach((s) => s?.removeEventListener("abort", onSignalAbort)); + for (const s of abortSignals) { + s?.removeEventListener("abort", onSignalAbort); + } } } diff --git a/packages/api/src/utils/client/response.ts b/packages/api/src/utils/client/response.ts index ed008273588a..626252b9aaca 100644 --- a/packages/api/src/utils/client/response.ts +++ b/packages/api/src/utils/client/response.ts @@ -26,16 +26,17 @@ export class ApiResponse extends Response { wireFormat(): WireFormat | null { if (this._wireFormat === undefined) { if (this.definition.resp.isEmpty) { - return (this._wireFormat = null); + this._wireFormat = null; + return this._wireFormat; } 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"); + this._wireFormat = null; + return this._wireFormat; } + throw Error("Content-Type header is required in response"); } const mediaType = parseContentTypeHeader(contentType); @@ -189,13 +190,15 @@ export class ApiResponse extends Response { private getErrorMessage(): string { const errBody = this.resolvedErrorBody(); try { - const errJson = JSON.parse(errBody) as {message?: string}; + const errJson = JSON.parse(errBody) as {message?: string; failures?: {message: string}[]}; if (errJson.message) { + if (errJson.failures) { + return `${errJson.message}\n` + errJson.failures.map((e) => e.message).join("\n"); + } return errJson.message; - } else { - return errBody; } - } catch (e) { + return errBody; + } catch (_e) { return errBody || this.statusText; } } diff --git a/packages/api/src/utils/codecs.ts b/packages/api/src/utils/codecs.ts index 36d905583098..c075d8592a46 100644 --- a/packages/api/src/utils/codecs.ts +++ b/packages/api/src/utils/codecs.ts @@ -1,4 +1,3 @@ -/* 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"; @@ -20,11 +19,11 @@ export type EmptyRequest = Record; export type EmptyResponseData = void; export type EmptyMeta = void; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: We can not use `unknown` type here export type AnyEndpoint = Endpoint; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: We can not use `unknown` type here export type EmptyRequestEndpoint = Endpoint; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: We can not use `unknown` type here export type EmptyResponseEndpoint = Endpoint; /** Shortcut for routes that have no params, query */ @@ -72,11 +71,11 @@ export const EmptyResponseCodec: ResponseCodec = { 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)) { + } + if (isBasicType(elementType)) { return new ListBasicType(elementType, limit) as unknown as ArrayType, unknown, unknown>; - } else { - throw Error(`Unknown type ${elementType.typeName}`); } + throw Error(`Unknown type ${elementType.typeName}`); } export function WithMeta(getType: (m: M) => Type): ResponseDataCodec { diff --git a/packages/api/src/utils/headers.ts b/packages/api/src/utils/headers.ts index 5f3c6e3e1dfc..7547ac022a7a 100644 --- a/packages/api/src/utils/headers.ts +++ b/packages/api/src/utils/headers.ts @@ -70,7 +70,7 @@ export function parseAcceptHeader(accept?: string, supported = SUPPORTED_MEDIA_T } const qvalue = +weight.replace("q=", ""); - if (isNaN(qvalue) || qvalue > 1 || qvalue <= 0) { + if (Number.isNaN(qvalue) || qvalue > 1 || qvalue <= 0) { // If we can't convert the qvalue to a valid number, move on return best; } diff --git a/packages/api/src/utils/httpStatusCode.ts b/packages/api/src/utils/httpStatusCode.ts index 572562e7da3d..2316f3d1db87 100644 --- a/packages/api/src/utils/httpStatusCode.ts +++ b/packages/api/src/utils/httpStatusCode.ts @@ -1,5 +1,3 @@ -"use strict"; - /** * Hypertext Transfer Protocol (HTTP) response status codes. * @see {@link https://www.rfc-editor.org/rfc/rfc7231#section-6} diff --git a/packages/api/src/utils/metadata.ts b/packages/api/src/utils/metadata.ts index 1eaa4132119f..2ee9b2f51f0e 100644 --- a/packages/api/src/utils/metadata.ts +++ b/packages/api/src/utils/metadata.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {ContainerType, ValueOf} from "@chainsafe/ssz"; import {ForkName} from "@lodestar/params"; import {StringType, ssz, stringType} from "@lodestar/types"; diff --git a/packages/api/src/utils/schema.ts b/packages/api/src/utils/schema.ts index 2d086fd8dfa9..3f297db6f665 100644 --- a/packages/api/src/utils/schema.ts +++ b/packages/api/src/utils/schema.ts @@ -1,3 +1,4 @@ +import {MediaType} from "./headers.js"; import {Endpoint, HeaderParams, PathParams, QueryParams} from "./types.js"; // Reasoning: Allows to declare JSON schemas for server routes in a succinct typesafe way. @@ -91,7 +92,16 @@ export function getFastifySchema(schemaDef: Schem const schema: {params?: JsonSchemaObj; querystring?: JsonSchemaObj; headers?: JsonSchemaObj; body?: JsonSchema} = {}; if (schemaDef.body != null) { - schema.body = getJsonSchemaItem(schemaDef.body); + schema.body = { + content: { + [MediaType.json]: { + schema: getJsonSchemaItem(schemaDef.body), + }, + [MediaType.ssz]: { + schema: {}, + }, + }, + }; } if (schemaDef.params) { diff --git a/packages/api/src/utils/serdes.ts b/packages/api/src/utils/serdes.ts index 233d7db9e7f8..9fb772a0bc63 100644 --- a/packages/api/src/utils/serdes.ts +++ b/packages/api/src/utils/serdes.ts @@ -1,4 +1,5 @@ -import {fromHexString, JsonPath, toHexString} from "@chainsafe/ssz"; +import {JsonPath} from "@chainsafe/ssz"; +import {fromHex, toHex} from "@lodestar/utils"; /** * Serialize proof path to JSON. @@ -17,9 +18,8 @@ export function querySerializeProofPathsArr(paths: JsonPath[]): string[] { export function queryParseProofPathsArr(pathStrs: string | string[]): JsonPath[] { if (Array.isArray(pathStrs)) { return pathStrs.map((pathStr) => queryParseProofPaths(pathStr)); - } else { - return [queryParseProofPaths(pathStrs)]; } + return [queryParseProofPaths(pathStrs)]; } /** @@ -49,7 +49,7 @@ export type U64Str = string; export function fromU64Str(u64Str: U64Str): number { const u64 = parseInt(u64Str, 10); - if (!isFinite(u64)) { + if (!Number.isFinite(u64)) { throw Error(`Invalid uin64 ${u64Str}`); } return u64; @@ -82,7 +82,7 @@ export function toGraffitiHex(utf8?: string): string | undefined { return undefined; } - const hex = toHexString(new TextEncoder().encode(utf8)); + const hex = toHex(new TextEncoder().encode(utf8)); if (hex.length > GRAFFITI_HEX_LENGTH) { // remove characters from the end if hex string is too long @@ -102,8 +102,8 @@ export function fromGraffitiHex(hex?: string): string | undefined { return undefined; } try { - return new TextDecoder("utf8").decode(fromHexString(hex)); - } catch { + return new TextDecoder("utf8").decode(fromHex(hex)); + } catch (_e) { // allow malformed graffiti hex string return hex; } diff --git a/packages/api/src/utils/server/handler.ts b/packages/api/src/utils/server/handler.ts index a3cd9a43a56a..035f4328141a 100644 --- a/packages/api/src/utils/server/handler.ts +++ b/packages/api/src/utils/server/handler.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import type * as fastify from "fastify"; import {HttpHeader, MediaType, SUPPORTED_MEDIA_TYPES, parseAcceptHeader, parseContentTypeHeader} from "../headers.js"; import { diff --git a/packages/api/src/utils/server/method.ts b/packages/api/src/utils/server/method.ts index 080c78869798..6fc99cc49a72 100644 --- a/packages/api/src/utils/server/method.ts +++ b/packages/api/src/utils/server/method.ts @@ -12,10 +12,9 @@ type ApplicationResponseObject = { : {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 ApplicationResponse = HasOnlyOptionalProps> extends true + ? ApplicationResponseObject | void + : ApplicationResponseObject; export type ApiContext = { /** diff --git a/packages/api/src/utils/server/parser.ts b/packages/api/src/utils/server/parser.ts index fd668b63757e..3300575a4845 100644 --- a/packages/api/src/utils/server/parser.ts +++ b/packages/api/src/utils/server/parser.ts @@ -2,22 +2,10 @@ 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; - + async (_request: fastify.FastifyRequest, payload: Buffer) => { // 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 diff --git a/packages/api/src/utils/types.ts b/packages/api/src/utils/types.ts index abe2f358320d..aef18b47d940 100644 --- a/packages/api/src/utils/types.ts +++ b/packages/api/src/utils/types.ts @@ -6,7 +6,7 @@ import {WireFormat} from "./wireFormat.js"; export type HasOnlyOptionalProps = { [K in keyof T]-?: object extends Pick ? never : K; -} extends {[_ in keyof T]: never} +} extends {[K2 in keyof T]: never} ? true : false; diff --git a/packages/api/test/perf/compileRouteUrlFormater.test.ts b/packages/api/test/perf/compileRouteUrlFormater.test.ts index ab16e1a5d14e..60d9e3fea8ee 100644 --- a/packages/api/test/perf/compileRouteUrlFormater.test.ts +++ b/packages/api/test/perf/compileRouteUrlFormater.test.ts @@ -1,7 +1,5 @@ import {compileRouteUrlFormatter} from "../../src/utils/urlFormat.js"; -/* eslint-disable no-console */ - describe("route parse", () => { it.skip("Benchmark compileRouteUrlFormatter", () => { const path = "/eth/v1/validator/:name/attester/:epoch"; diff --git a/packages/api/test/unit/beacon/genericServerTest/beacon.test.ts b/packages/api/test/unit/beacon/genericServerTest/beacon.test.ts index a722e72a4c27..f8c13b33afb6 100644 --- a/packages/api/test/unit/beacon/genericServerTest/beacon.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/beacon.test.ts @@ -8,7 +8,6 @@ import {testData} from "../testData/beacon.js"; describe("beacon / beacon", () => { runGenericServerTest( - // eslint-disable-next-line @typescript-eslint/naming-convention createChainForkConfig({...defaultChainConfig, ALTAIR_FORK_EPOCH: 1, BELLATRIX_FORK_EPOCH: 2}), getClient, getRoutes, diff --git a/packages/api/test/unit/beacon/genericServerTest/config.test.ts b/packages/api/test/unit/beacon/genericServerTest/config.test.ts index 8b924cf07693..91075f96d1e3 100644 --- a/packages/api/test/unit/beacon/genericServerTest/config.test.ts +++ b/packages/api/test/unit/beacon/genericServerTest/config.test.ts @@ -6,8 +6,6 @@ import {getRoutes} from "../../../../src/beacon/server/config.js"; import {runGenericServerTest} from "../../../utils/genericServerTest.js"; import {testData} from "../testData/config.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - describe("beacon / config", () => { runGenericServerTest(config, getClient, getRoutes, testData); diff --git a/packages/api/test/unit/beacon/oapiSpec.test.ts b/packages/api/test/unit/beacon/oapiSpec.test.ts index a84b2cc36767..f6a38505e264 100644 --- a/packages/api/test/unit/beacon/oapiSpec.test.ts +++ b/packages/api/test/unit/beacon/oapiSpec.test.ts @@ -18,17 +18,15 @@ import {testData as validatorTestData} from "./testData/validator.js"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const version = "v2.6.0-alpha.1"; +const version = "v3.0.0-alpha.6"; const openApiFile: OpenApiFile = { - url: `https://raw.githubusercontent.com/nflaig/beacon-api-spec/main/${version}/beacon-node-oapi.json`, + url: `https://github.com/ethereum/beacon-APIs/releases/download/${version}/beacon-node-oapi.json`, filepath: path.join(__dirname, "../../../oapi-schemas/beacon-node-oapi.json"), version: RegExp(version), }; -// eslint-disable-next-line @typescript-eslint/naming-convention const config = createChainForkConfig({...defaultChainConfig, ALTAIR_FORK_EPOCH: 1, BELLATRIX_FORK_EPOCH: 2}); const definitions = { @@ -57,9 +55,6 @@ const ignoredOperations = [ /* missing route */ "getDepositSnapshot", // Won't fix for now, see https://github.com/ChainSafe/lodestar/issues/5697 "getNextWithdrawals", // https://github.com/ChainSafe/lodestar/issues/5696 - "getDebugForkChoice", // https://github.com/ChainSafe/lodestar/issues/5700 - /* Must support ssz response body */ - "getLightClientUpdatesByRange", // https://github.com/ChainSafe/lodestar/issues/6841 ]; const ignoredProperties: Record = { diff --git a/packages/api/test/unit/beacon/testData/beacon.ts b/packages/api/test/unit/beacon/testData/beacon.ts index b0e958f4bc58..23ab13147454 100644 --- a/packages/api/test/unit/beacon/testData/beacon.ts +++ b/packages/api/test/unit/beacon/testData/beacon.ts @@ -191,6 +191,13 @@ export const testData: GenericServerTestCases = { args: {stateId: "head", validatorIds: [pubkeyHex, 1300], statuses: ["active_ongoing"]}, res: {data: [validatorResponse], meta: {executionOptimistic: true, finalized: false}}, }, + postStateValidatorIdentities: { + args: {stateId: "head", validatorIds: [1300]}, + res: { + data: [{index: 1300, pubkey: ssz.BLSPubkey.defaultValue(), activationEpoch: 1}], + meta: {executionOptimistic: true, finalized: false}, + }, + }, getStateValidator: { args: {stateId: "head", validatorId: pubkeyHex}, res: {data: validatorResponse, meta: {executionOptimistic: true, finalized: false}}, diff --git a/packages/api/test/unit/beacon/testData/debug.ts b/packages/api/test/unit/beacon/testData/debug.ts index aac3b379ff4d..cb2799939ae3 100644 --- a/packages/api/test/unit/beacon/testData/debug.ts +++ b/packages/api/test/unit/beacon/testData/debug.ts @@ -4,13 +4,41 @@ import {ssz} from "@lodestar/types"; import {Endpoints} from "../../../../src/beacon/routes/debug.js"; import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; -const rootHex = toHexString(Buffer.alloc(32, 1)); +const root = new Uint8Array(32).fill(1); +const rootHex = toHexString(root); export const testData: GenericServerTestCases = { getDebugChainHeadsV2: { args: undefined, res: {data: [{slot: 1, root: rootHex, executionOptimistic: true}]}, }, + getDebugForkChoice: { + args: undefined, + res: { + data: { + justifiedCheckpoint: { + epoch: 2, + root, + }, + finalizedCheckpoint: { + epoch: 1, + root, + }, + forkChoiceNodes: [ + { + slot: 1, + blockRoot: rootHex, + parentRoot: rootHex, + justifiedEpoch: 1, + finalizedEpoch: 1, + weight: 1, + validity: "valid", + executionBlockHash: rootHex, + }, + ], + }, + }, + }, getProtoArrayNodes: { args: undefined, res: { diff --git a/packages/api/test/unit/beacon/testData/events.ts b/packages/api/test/unit/beacon/testData/events.ts index 8a7610a26836..3d2ffb708965 100644 --- a/packages/api/test/unit/beacon/testData/events.ts +++ b/packages/api/test/unit/beacon/testData/events.ts @@ -5,8 +5,6 @@ import {GenericServerTestCases} from "../../../utils/genericServerTest.js"; const abortController = new AbortController(); -/* eslint-disable @typescript-eslint/naming-convention */ - export const testData: GenericServerTestCases = { eventstream: { args: {topics: [EventType.head, EventType.chainReorg], signal: abortController.signal, onEvent: () => {}}, diff --git a/packages/api/test/unit/beacon/testData/lightclient.ts b/packages/api/test/unit/beacon/testData/lightclient.ts index b86e1f338329..f405d514fc5d 100644 --- a/packages/api/test/unit/beacon/testData/lightclient.ts +++ b/packages/api/test/unit/beacon/testData/lightclient.ts @@ -14,7 +14,7 @@ const signatureSlot = ssz.Slot.defaultValue(); export const testData: GenericServerTestCases = { getLightClientUpdatesByRange: { args: {startPeriod: 1, count: 2}, - res: {data: [lightClientUpdate], meta: {versions: [ForkName.bellatrix]}}, + res: {data: [lightClientUpdate, lightClientUpdate], meta: {versions: [ForkName.altair, ForkName.altair]}}, }, getLightClientOptimisticUpdate: { args: undefined, diff --git a/packages/api/test/unit/builder/builder.test.ts b/packages/api/test/unit/builder/builder.test.ts index 045a66496b6f..d2e25916069e 100644 --- a/packages/api/test/unit/builder/builder.test.ts +++ b/packages/api/test/unit/builder/builder.test.ts @@ -10,7 +10,6 @@ describe("builder", () => { runGenericServerTest( createChainForkConfig({ ...defaultChainConfig, - /* eslint-disable @typescript-eslint/naming-convention */ ALTAIR_FORK_EPOCH: 0, BELLATRIX_FORK_EPOCH: 0, DENEB_FORK_EPOCH: 0, diff --git a/packages/api/test/unit/builder/oapiSpec.test.ts b/packages/api/test/unit/builder/oapiSpec.test.ts index 60168f71d8d8..d972b64905e5 100644 --- a/packages/api/test/unit/builder/oapiSpec.test.ts +++ b/packages/api/test/unit/builder/oapiSpec.test.ts @@ -10,7 +10,6 @@ import {testData} from "./testData.js"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); const version = "v0.2.0"; @@ -23,7 +22,6 @@ const openApiFile: OpenApiFile = { }; const definitions = getDefinitions( - // eslint-disable-next-line @typescript-eslint/naming-convention createChainForkConfig({...defaultChainConfig, ALTAIR_FORK_EPOCH: 0, BELLATRIX_FORK_EPOCH: 0}) ); diff --git a/packages/api/test/unit/builder/testData.ts b/packages/api/test/unit/builder/testData.ts index a23823702b6d..a807620258df 100644 --- a/packages/api/test/unit/builder/testData.ts +++ b/packages/api/test/unit/builder/testData.ts @@ -23,7 +23,7 @@ export const testData: GenericServerTestCases = { res: {data: ssz.bellatrix.SignedBuilderBid.defaultValue(), meta: {version: ForkName.bellatrix}}, }, submitBlindedBlock: { - args: {signedBlindedBlock: ssz.deneb.SignedBlindedBeaconBlock.defaultValue()}, + args: {signedBlindedBlock: {data: ssz.deneb.SignedBlindedBeaconBlock.defaultValue()}}, res: {data: ssz.bellatrix.ExecutionPayload.defaultValue(), meta: {version: ForkName.bellatrix}}, }, }; diff --git a/packages/api/test/unit/client/fetch.test.ts b/packages/api/test/unit/client/fetch.test.ts index 80e5f58b164a..1faf7d979a02 100644 --- a/packages/api/test/unit/client/fetch.test.ts +++ b/packages/api/test/unit/client/fetch.test.ts @@ -3,7 +3,7 @@ import http from "node:http"; import {describe, it, expect, afterEach} from "vitest"; import {FetchError, FetchErrorType, fetch} from "../../../src/utils/client/fetch.js"; -describe("FetchError", function () { +describe("FetchError", () => { const port = 37421; const randomHex = crypto.randomBytes(32).toString("hex"); @@ -87,12 +87,11 @@ describe("FetchError", function () { const afterHooks: (() => Promise)[] = []; - afterEach(async function () { + afterEach(async () => { while (afterHooks.length) { const afterHook = afterHooks.pop(); if (afterHook) await afterHook().catch((e: Error) => { - // eslint-disable-next-line no-console console.error("Error in afterEach hook", e); }); } @@ -101,7 +100,7 @@ describe("FetchError", function () { for (const testCase of testCases) { const {id, url = `http://localhost:${port}`, requestListener, signalHandler} = testCase; - it(id, async function () { + it(id, async () => { if (requestListener) { const server = http.createServer(requestListener); await new Promise((resolve) => server.listen(port, resolve)); diff --git a/packages/api/test/unit/client/httpClientFallback.test.ts b/packages/api/test/unit/client/httpClientFallback.test.ts index ea20c3f33982..9fcde27de072 100644 --- a/packages/api/test/unit/client/httpClientFallback.test.ts +++ b/packages/api/test/unit/client/httpClientFallback.test.ts @@ -56,12 +56,12 @@ describe("httpClient fallback", () => { // 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 new Response(null, {status: 200}); + + throw Error(`test_error_server_${i}`); } + + return new Response(null, {status: 200}); }); }); @@ -81,7 +81,6 @@ describe("httpClient fallback", () => { fetchStub.mockClear(); - // eslint-disable-next-line no-console if (DEBUG_LOGS) console.log("completed assertions step", step); } diff --git a/packages/api/test/unit/client/urlFormat.test.ts b/packages/api/test/unit/client/urlFormat.test.ts index dc86e2674e13..5132044b7631 100644 --- a/packages/api/test/unit/client/urlFormat.test.ts +++ b/packages/api/test/unit/client/urlFormat.test.ts @@ -7,8 +7,6 @@ import { urlToTokens, } from "../../../src/utils/urlFormat.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - describe("utils / urlFormat", () => { const testCases: { urlTemplate: string; diff --git a/packages/api/test/unit/keymanager/oapiSpec.test.ts b/packages/api/test/unit/keymanager/oapiSpec.test.ts index 97011f5d3660..aab4e1823ab0 100644 --- a/packages/api/test/unit/keymanager/oapiSpec.test.ts +++ b/packages/api/test/unit/keymanager/oapiSpec.test.ts @@ -9,7 +9,6 @@ import {testData} from "./testData.js"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); const version = "v1.1.0"; diff --git a/packages/api/test/utils/checkAgainstSpec.ts b/packages/api/test/utils/checkAgainstSpec.ts index 5e339efcb822..a3f943ad4816 100644 --- a/packages/api/test/utils/checkAgainstSpec.ts +++ b/packages/api/test/utils/checkAgainstSpec.ts @@ -93,13 +93,13 @@ export function runTestCheckAgainstSpec>( } }); - it(`${operationId}_route`, function () { + it(`${operationId}_route`, () => { expect(routeDef.method.toLowerCase()).toBe(routeSpec.method.toLowerCase()); expect(routeDef.url).toBe(routeSpec.url); }); if (requestSchema != null) { - it(`${operationId}_request`, function () { + it(`${operationId}_request`, () => { const reqJson = isRequestWithoutBody(routeDef) ? routeDef.req.writeReq(testData.args) : (routeDef.req as RequestWithBodyCodec).writeReqJson(testData.args); @@ -127,7 +127,7 @@ export function runTestCheckAgainstSpec>( expect(reqSsz.body).toBeInstanceOf(Uint8Array); expect(reqCodec.onlySupport).not.toBe(WireFormat.json); - } catch { + } catch (_e) { throw Error("Must support ssz request body"); } } @@ -135,7 +135,7 @@ export function runTestCheckAgainstSpec>( } if (responseOkSchema) { - it(`${operationId}_response`, function () { + it(`${operationId}_response`, () => { 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)); @@ -167,7 +167,7 @@ export function runTestCheckAgainstSpec>( expect(sszBytes).toBeInstanceOf(Uint8Array); expect(routeDef.resp.onlySupport).not.toBe(WireFormat.json); - } catch { + } catch (_e) { throw Error("Must support ssz response body"); } } @@ -183,7 +183,6 @@ function validateSchema(schema: Parameters[0], json: unknown try { validate = ajv.compile(schema); } catch (e) { - // eslint-disable-next-line no-console console.error(JSON.stringify(schema, null, 2)); (e as Error).message = `${id} schema - ${(e as Error).message}`; throw e; @@ -219,7 +218,9 @@ type StringifiedProperty = string | StringifiedProperty[]; function stringifyProperty(value: unknown): StringifiedProperty { if (typeof value === "number") { return value.toString(10); - } else if (Array.isArray(value)) { + } + + if (Array.isArray(value)) { return value.map(stringifyProperty); } return String(value); diff --git a/packages/api/test/utils/parseOpenApiSpec.ts b/packages/api/test/utils/parseOpenApiSpec.ts index 7527fb61abaa..d04827fd2011 100644 --- a/packages/api/test/utils/parseOpenApiSpec.ts +++ b/packages/api/test/utils/parseOpenApiSpec.ts @@ -105,7 +105,6 @@ export function parseOpenApiSpec(openApiJson: OpenApiJson): Map { - if (obj.allOf && obj.allOf.every((s) => s.enum)) { + if (obj.allOf?.every((s) => s.enum)) { obj.allOf = [obj.allOf[0]]; } }); diff --git a/packages/api/test/utils/utils.ts b/packages/api/test/utils/utils.ts index d78378e57e19..d3ecbc964435 100644 --- a/packages/api/test/utils/utils.ts +++ b/packages/api/test/utils/utils.ts @@ -14,15 +14,14 @@ export function getTestServer(): {server: FastifyInstance; start: () => Promise< addSszContentTypeParser(server); server.addHook("onError", (_request, _reply, error, done) => { - // eslint-disable-next-line no-console console.log(`onError: ${error.toString()}`); done(); }); const start = (): Promise => new Promise((resolve, reject) => { - server.listen({port: 0}, function (err, address) { - if (err !== null && err != undefined) { + server.listen({port: 0}, (err, address) => { + if (err != null) { reject(err); } else { resolve(address); diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index ade89ebc6cc1..7e400b69ad3f 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.22.0", + "version": "1.23.0", "type": "module", "exports": { ".": { @@ -75,8 +75,8 @@ "build:release": "yarn clean && yarn run build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit && yarn test:e2e", "test:unit:minimal": "LODESTAR_PRESET=minimal vitest --run --dir test/unit/", "test:unit:mainnet": "LODESTAR_PRESET=mainnet vitest --run --dir test/unit-mainnet", @@ -95,7 +95,7 @@ }, "dependencies": { "@chainsafe/as-sha256": "^0.5.0", - "@chainsafe/blst": "^2.0.3", + "@chainsafe/blst": "^2.2.0", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", "@chainsafe/libp2p-gossipsub": "^13.0.0", @@ -103,13 +103,14 @@ "@chainsafe/libp2p-noise": "^15.0.0", "@chainsafe/persistent-merkle-tree": "^0.8.0", "@chainsafe/prometheus-gc-stats": "^1.0.0", - "@chainsafe/ssz": "^0.17.1", + "@chainsafe/pubkey-index-map": "2.0.0", + "@chainsafe/ssz": "^0.18.0", "@chainsafe/threads": "^1.11.1", "@ethersproject/abi": "^5.7.0", - "@fastify/bearer-auth": "^9.0.0", - "@fastify/cors": "^8.2.1", - "@fastify/swagger": "^8.10.0", - "@fastify/swagger-ui": "^1.9.3", + "@fastify/bearer-auth": "^10.0.1", + "@fastify/cors": "^10.0.1", + "@fastify/swagger": "^9.0.0", + "@fastify/swagger-ui": "^5.0.1", "@libp2p/bootstrap": "^10.0.21", "@libp2p/identify": "^1.0.20", "@libp2p/interface": "^1.3.0", @@ -119,24 +120,24 @@ "@libp2p/peer-id-factory": "^4.1.0", "@libp2p/prometheus-metrics": "^3.0.21", "@libp2p/tcp": "9.0.23", - "@lodestar/api": "^1.22.0", - "@lodestar/config": "^1.22.0", - "@lodestar/db": "^1.22.0", - "@lodestar/fork-choice": "^1.22.0", - "@lodestar/light-client": "^1.22.0", - "@lodestar/logger": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/reqresp": "^1.22.0", - "@lodestar/state-transition": "^1.22.0", - "@lodestar/types": "^1.22.0", - "@lodestar/utils": "^1.22.0", - "@lodestar/validator": "^1.22.0", + "@lodestar/api": "^1.23.0", + "@lodestar/config": "^1.23.0", + "@lodestar/db": "^1.23.0", + "@lodestar/fork-choice": "^1.23.0", + "@lodestar/light-client": "^1.23.0", + "@lodestar/logger": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/reqresp": "^1.23.0", + "@lodestar/state-transition": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0", + "@lodestar/validator": "^1.23.0", "@multiformats/multiaddr": "^12.1.3", "c-kzg": "^2.1.2", "datastore-core": "^9.1.1", "datastore-level": "^10.1.1", "deepmerge": "^4.3.1", - "fastify": "^4.27.0", + "fastify": "^5.0.0", "interface-datastore": "^8.2.7", "it-all": "^3.0.4", "it-pipe": "^3.0.1", 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 65e7b9373a22..2d36505d822a 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -15,6 +15,7 @@ import { SignedBeaconBlock, SignedBeaconBlockOrContents, SignedBlindedBeaconBlock, + WithOptionalBytes, } from "@lodestar/types"; import { BlockSource, @@ -176,9 +177,8 @@ export function getBeaconBlockApi({ const message = `Equivocation checks not yet implemented for broadcastValidation=${broadcastValidation}`; if (chain.opts.broadcastValidationStrictness === "error") { throw Error(message); - } else { - chain.logger.warn(message, valLogMeta); } + chain.logger.warn(message, valLogMeta); } break; } @@ -193,9 +193,8 @@ export function getBeaconBlockApi({ const message = `Broadcast validation of ${broadcastValidation} type not implemented yet`; if (chain.opts.broadcastValidationStrictness === "error") { throw Error(message); - } else { - chain.logger.warn(message, valLogMeta); } + chain.logger.warn(message, valLogMeta); } } @@ -224,15 +223,17 @@ export function getBeaconBlockApi({ () => network.publishBeaconBlock(signedBlock) as Promise, () => // there is no rush to persist block since we published it to gossip anyway - chain.processBlock(blockForImport, {...opts, eagerPersistBlock: false}).catch((e) => { - if (e instanceof BlockError && e.type.code === BlockErrorCode.PARENT_UNKNOWN) { - network.events.emit(NetworkEvent.unknownBlockParent, { - blockInput: blockForImport, - peer: IDENTITY_PEER_ID, - }); - } - throw e; - }), + chain + .processBlock(blockForImport, {...opts, eagerPersistBlock: false}) + .catch((e) => { + if (e instanceof BlockError && e.type.code === BlockErrorCode.PARENT_UNKNOWN) { + network.events.emit(NetworkEvent.unknownBlockParent, { + blockInput: blockForImport, + peer: IDENTITY_PEER_ID, + }); + } + throw e; + }), ]; await promiseAllMaybeAsync(publishPromises); }; @@ -258,25 +259,28 @@ export function getBeaconBlockApi({ chain.logger.debug("Reconstructing signedBlockOrContents", {slot, blockRoot, source}); const contents = executionPayload - ? chain.producedContentsCache.get(toRootHex(executionPayload.blockHash)) ?? null + ? (chain.producedContentsCache.get(toRootHex(executionPayload.blockHash)) ?? null) : null; const signedBlockOrContents = reconstructFullBlockOrContents(signedBlindedBlock, {executionPayload, contents}); chain.logger.info("Publishing assembled block", {slot, blockRoot, source}); return publishBlock({signedBlockOrContents}, {...context, sszBytes: null}, opts); - } else { - const source = ProducedBlockSource.builder; - chain.logger.debug("Reconstructing signedBlockOrContents", {slot, blockRoot, source}); + } - const signedBlockOrContents = await reconstructBuilderBlockOrContents(chain, signedBlindedBlock); + const source = ProducedBlockSource.builder; + chain.logger.debug("Reconstructing signedBlockOrContents", {slot, blockRoot, source}); - // the full block is published by relay and it's possible that the block is already known to us - // by gossip - // - // see: https://github.com/ChainSafe/lodestar/issues/5404 - chain.logger.info("Publishing assembled block", {slot, blockRoot, source}); - return publishBlock({signedBlockOrContents}, {...context, sszBytes: null}, {...opts, ignoreIfKnown: true}); - } + const signedBlockOrContents = await reconstructBuilderBlockOrContents(chain, { + data: signedBlindedBlock, + bytes: context?.sszBytes, + }); + + // the full block is published by relay and it's possible that the block is already known to us + // by gossip + // + // see: https://github.com/ChainSafe/lodestar/issues/5404 + chain.logger.info("Publishing assembled block", {slot, blockRoot, source}); + return publishBlock({signedBlockOrContents}, {...context, sszBytes: null}, {...opts, ignoreIfKnown: true}); }; return { @@ -507,7 +511,7 @@ export function getBeaconBlockApi({ async function reconstructBuilderBlockOrContents( chain: ApiModules["chain"], - signedBlindedBlock: SignedBlindedBeaconBlock + signedBlindedBlock: WithOptionalBytes ): Promise { const executionBuilder = chain.executionBuilder; if (!executionBuilder) { 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 fe4fc5ca3dc0..44152ff4b8a9 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/utils.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/utils.ts @@ -50,7 +50,7 @@ export function resolveBlockId(forkChoice: IForkChoice, blockId: routes.beacon.B // block id must be slot const blockSlot = parseInt(blockId, 10); - if (isNaN(blockSlot) && isNaN(blockSlot - 0)) { + if (Number.isNaN(blockSlot) && Number.isNaN(blockSlot - 0)) { throw new ValidationError(`Invalid block id '${blockId}'`, "blockId"); } return blockSlot; 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 398238aa2508..9343dd67a399 100644 --- a/packages/beacon-node/src/api/impl/beacon/pool/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/pool/index.ts @@ -16,7 +16,7 @@ import { SyncCommitteeError, } from "../../../../chain/errors/index.js"; import {validateGossipFnRetryUnknownRoot} from "../../../../network/processor/gossipHandlers.js"; -import {ApiError} from "../../errors.js"; +import {ApiError, FailureList, IndexedError} from "../../errors.js"; export function getBeaconPoolApi({ chain, @@ -88,13 +88,12 @@ export function getBeaconPoolApi({ async submitPoolAttestationsV2({signedAttestations}) { const seenTimestampSec = Date.now() / 1000; - const errors: Error[] = []; + const failures: FailureList = []; await Promise.all( signedAttestations.map(async (attestation, i) => { try { const fork = chain.config.getForkName(chain.clock.currentSlot); - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const validateFn = () => validateApiAttestation(fork, chain, {attestation, serializedData: null}); const {slot, beaconBlockRoot} = attestation.data; // when a validator is configured with multiple beacon node urls, this attestation data may come from another beacon node @@ -127,7 +126,7 @@ export function getBeaconPoolApi({ return; } - errors.push(e as Error); + failures.push({index: i, message: (e as Error).message}); logger.error(`Error on submitPoolAttestations [${i}]`, logCtx, e as Error); if (e instanceof AttestationError && e.action === GossipAction.REJECT) { chain.persistInvalidSszValue(ssz.phase0.Attestation, attestation, "api_reject"); @@ -136,10 +135,8 @@ export function getBeaconPoolApi({ }) ); - if (errors.length > 1) { - throw Error("Multiple errors on submitPoolAttestations\n" + errors.map((e) => e.message).join("\n")); - } else if (errors.length === 1) { - throw errors[0]; + if (failures.length > 0) { + throw new IndexedError("Error processing attestations", failures); } }, @@ -168,7 +165,7 @@ export function getBeaconPoolApi({ }, async submitPoolBLSToExecutionChange({blsToExecutionChanges}) { - const errors: Error[] = []; + const failures: FailureList = []; await Promise.all( blsToExecutionChanges.map(async (blsToExecutionChange, i) => { @@ -184,7 +181,7 @@ export function getBeaconPoolApi({ await network.publishBlsToExecutionChange(blsToExecutionChange); } } catch (e) { - errors.push(e as Error); + failures.push({index: i, message: (e as Error).message}); logger.error( `Error on submitPoolBLSToExecutionChange [${i}]`, {validatorIndex: blsToExecutionChange.message.validatorIndex}, @@ -194,10 +191,8 @@ export function getBeaconPoolApi({ }) ); - if (errors.length > 1) { - throw Error("Multiple errors on submitPoolBLSToExecutionChange\n" + errors.map((e) => e.message).join("\n")); - } else if (errors.length === 1) { - throw errors[0]; + if (failures.length > 0) { + throw new IndexedError("Error processing BLS to execution changes", failures); } }, @@ -221,7 +216,7 @@ export function getBeaconPoolApi({ // TODO: Fetch states at signature slots const state = chain.getHeadState(); - const errors: Error[] = []; + const failures: FailureList = []; await Promise.all( signatures.map(async (signature, i) => { @@ -261,7 +256,7 @@ export function getBeaconPoolApi({ return; } - errors.push(e as Error); + failures.push({index: i, message: (e as Error).message}); logger.debug( `Error on submitPoolSyncCommitteeSignatures [${i}]`, {slot: signature.slot, validatorIndex: signature.validatorIndex}, @@ -274,10 +269,8 @@ export function getBeaconPoolApi({ }) ); - if (errors.length > 1) { - throw Error("Multiple errors on submitPoolSyncCommitteeSignatures\n" + errors.map((e) => e.message).join("\n")); - } else if (errors.length === 1) { - throw errors[0]; + if (failures.length > 0) { + throw new IndexedError("Error processing sync committee signatures", failures); } }, }; 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 9d9646ee8cf3..a1e17b80ea84 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/index.ts @@ -9,15 +9,11 @@ import { getRandaoMix, } from "@lodestar/state-transition"; import {EPOCHS_PER_HISTORICAL_VECTOR} from "@lodestar/params"; +import {getValidatorStatus} from "@lodestar/types"; +import {fromHex} from "@lodestar/utils"; import {ApiError} from "../../errors.js"; import {ApiModules} from "../../types.js"; -import { - filterStateValidatorsByStatus, - getStateValidatorIndex, - getValidatorStatus, - getStateResponse, - toValidatorResponse, -} from "./utils.js"; +import {filterStateValidatorsByStatus, getStateValidatorIndex, getStateResponse, toValidatorResponse} from "./utils.js"; export function getBeaconStateApi({ chain, @@ -104,7 +100,9 @@ export function getBeaconStateApi({ data: validatorResponses, meta: {executionOptimistic, finalized}, }; - } else if (statuses.length) { + } + + if (statuses.length) { const validatorsByStatus = filterStateValidatorsByStatus(statuses, state, pubkey2index, currentEpoch); return { data: validatorsByStatus, @@ -130,6 +128,37 @@ export function getBeaconStateApi({ return this.getStateValidators(args, context); }, + async postStateValidatorIdentities({stateId, validatorIds = []}) { + const {state, executionOptimistic, finalized} = await getStateResponse(chain, stateId); + const {pubkey2index} = chain.getHeadState().epochCtx; + + let validatorIdentities: routes.beacon.ValidatorIdentities; + + if (validatorIds.length) { + validatorIdentities = []; + for (const id of validatorIds) { + const resp = getStateValidatorIndex(id, state, pubkey2index); + if (resp.valid) { + const index = resp.validatorIndex; + const {pubkey, activationEpoch} = state.validators.getReadonly(index); + validatorIdentities.push({index, pubkey, activationEpoch}); + } + } + } else { + const validatorsArr = state.validators.getAllReadonlyValues(); + validatorIdentities = new Array(validatorsArr.length) as routes.beacon.ValidatorIdentities; + for (let i = 0; i < validatorsArr.length; i++) { + const {pubkey, activationEpoch} = validatorsArr[i]; + validatorIdentities[i] = {index: i, pubkey, activationEpoch}; + } + } + + return { + data: validatorIdentities, + meta: {executionOptimistic, finalized}, + }; + }, + async getStateValidator({stateId, validatorId}) { const {state, executionOptimistic, finalized} = await getStateResponse(chain, stateId); const {pubkey2index} = chain.getHeadState().epochCtx; @@ -164,7 +193,7 @@ export function getBeaconStateApi({ } balances.push({index: id, balance: state.balances.get(id)}); } else { - const index = headState.epochCtx.pubkey2index.get(id); + const index = headState.epochCtx.pubkey2index.get(fromHex(id)); if (index != null && index <= state.validators.length) { balances.push({index, balance: state.balances.get(index)}); } @@ -202,7 +231,14 @@ export function getBeaconStateApi({ const epoch = filters.epoch ?? computeEpochAtSlot(state.slot); const startSlot = computeStartSlotAtEpoch(epoch); - const shuffling = stateCached.epochCtx.getShufflingAtEpoch(epoch); + const decisionRoot = stateCached.epochCtx.getShufflingDecisionRoot(epoch); + const shuffling = await chain.shufflingCache.get(epoch, decisionRoot); + if (!shuffling) { + throw new ApiError( + 500, + `No shuffling found to calculate committees for epoch: ${epoch} and decisionRoot: ${decisionRoot}` + ); + } const committees = shuffling.committees; const committeesFlat = committees.flatMap((slotCommittees, slotInEpoch) => { const slot = startSlot + slotInEpoch; 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 40b1e2815263..392262a563be 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/utils.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/utils.ts @@ -1,7 +1,8 @@ +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {routes} from "@lodestar/api"; -import {FAR_FUTURE_EPOCH, GENESIS_SLOT} from "@lodestar/params"; -import {BeaconStateAllForks, PubkeyIndexMap} from "@lodestar/state-transition"; -import {BLSPubkey, Epoch, phase0, RootHex, Slot, ValidatorIndex} from "@lodestar/types"; +import {GENESIS_SLOT} from "@lodestar/params"; +import {BeaconStateAllForks} from "@lodestar/state-transition"; +import {BLSPubkey, Epoch, getValidatorStatus, phase0, RootHex, Slot, ValidatorIndex} from "@lodestar/types"; import {fromHex} from "@lodestar/utils"; import {CheckpointWithHex, IForkChoice} from "@lodestar/fork-choice"; import {IBeaconChain} from "../../../../chain/index.js"; @@ -33,7 +34,7 @@ export function resolveStateId( // id must be slot const blockSlot = parseInt(String(stateId), 10); - if (isNaN(blockSlot) && isNaN(blockSlot - 0)) { + if (Number.isNaN(blockSlot) && Number.isNaN(blockSlot - 0)) { throw new ValidationError(`Invalid block id '${stateId}'`, "blockId"); } @@ -82,36 +83,26 @@ export async function getStateResponseWithRegen( return res; } -/** - * Get the status of the validator - * based on conditions outlined in https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ - */ -export function getValidatorStatus(validator: phase0.Validator, currentEpoch: Epoch): routes.beacon.ValidatorStatus { - // pending - if (validator.activationEpoch > currentEpoch) { - if (validator.activationEligibilityEpoch === FAR_FUTURE_EPOCH) { - return "pending_initialized"; - } else if (validator.activationEligibilityEpoch < FAR_FUTURE_EPOCH) { - return "pending_queued"; - } - } - // active - if (validator.activationEpoch <= currentEpoch && currentEpoch < validator.exitEpoch) { - if (validator.exitEpoch === FAR_FUTURE_EPOCH) { - return "active_ongoing"; - } else if (validator.exitEpoch < FAR_FUTURE_EPOCH) { - return validator.slashed ? "active_slashed" : "active_exiting"; - } - } - // exited - if (validator.exitEpoch <= currentEpoch && currentEpoch < validator.withdrawableEpoch) { - return validator.slashed ? "exited_slashed" : "exited_unslashed"; - } - // withdrawal - if (validator.withdrawableEpoch <= currentEpoch) { - return validator.effectiveBalance !== 0 ? "withdrawal_possible" : "withdrawal_done"; +type GeneralValidatorStatus = "active" | "pending" | "exited" | "withdrawal"; + +function mapToGeneralStatus(subStatus: routes.beacon.ValidatorStatus): GeneralValidatorStatus { + switch (subStatus) { + case "active_ongoing": + case "active_exiting": + case "active_slashed": + return "active"; + case "pending_initialized": + case "pending_queued": + return "pending"; + case "exited_slashed": + case "exited_unslashed": + return "exited"; + case "withdrawal_possible": + case "withdrawal_done": + return "withdrawal"; + default: + throw new Error(`Unknown substatus: ${subStatus}`); } - throw new Error("ValidatorStatus unknown"); } export function toValidatorResponse( @@ -140,9 +131,10 @@ export function filterStateValidatorsByStatus( for (const validator of validatorsArr) { const validatorStatus = getValidatorStatus(validator, currentEpoch); + const generalStatus = mapToGeneralStatus(validatorStatus); const resp = getStateValidatorIndex(validator.pubkey, state, pubkey2index); - if (resp.valid && statusSet.has(validatorStatus)) { + if (resp.valid && (statusSet.has(validatorStatus) || statusSet.has(generalStatus))) { responses.push( toValidatorResponse(resp.validatorIndex, validator, state.balances.get(resp.validatorIndex), currentEpoch) ); @@ -165,7 +157,7 @@ export function getStateValidatorIndex( if (id.startsWith("0x")) { try { id = fromHex(id); - } catch (e) { + } catch (_e) { return {valid: false, code: 400, reason: "Invalid pubkey hex encoding"}; } } else { @@ -187,7 +179,7 @@ export function getStateValidatorIndex( // typeof id === Uint8Array const validatorIndex = pubkey2index.get(id); - if (validatorIndex === undefined) { + if (validatorIndex === null) { return {valid: false, code: 404, reason: "Validator pubkey not found in state"}; } if (validatorIndex >= state.validators.length) { diff --git a/packages/beacon-node/src/api/impl/config/constants.ts b/packages/beacon-node/src/api/impl/config/constants.ts index 4b239ee4cac6..e951da35bdd2 100644 --- a/packages/beacon-node/src/api/impl/config/constants.ts +++ b/packages/beacon-node/src/api/impl/config/constants.ts @@ -37,13 +37,10 @@ import { BLOB_TX_TYPE, VERSIONED_HASH_VERSION_KZG, COMPOUNDING_WITHDRAWAL_PREFIX, - DOMAIN_CONSOLIDATION, UNSET_DEPOSIT_REQUESTS_START_INDEX, FULL_EXIT_REQUEST_AMOUNT, } from "@lodestar/params"; -/* eslint-disable @typescript-eslint/naming-convention */ - /** * Hand-picked list of constants declared in consensus-spec .md files. * This list is asserted to be up-to-date with the test `test/e2e/api/impl/config.test.ts` @@ -71,7 +68,6 @@ export const specConstants = { DOMAIN_SELECTION_PROOF, DOMAIN_AGGREGATE_AND_PROOF, DOMAIN_APPLICATION_BUILDER, - DOMAIN_CONSOLIDATION, // phase0/validator.md TARGET_AGGREGATORS_PER_COMMITTEE, diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index 4edb8ba9b2dd..e5b6450b206f 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -1,6 +1,8 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; +import {ExecutionStatus} from "@lodestar/fork-choice"; import {BeaconState} from "@lodestar/types"; +import {ZERO_HASH_HEX} from "@lodestar/params"; import {getStateResponseWithRegen} from "../beacon/state/utils.js"; import {ApiModules} from "../types.js"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; @@ -22,6 +24,35 @@ export function getDebugApi({ }; }, + async getDebugForkChoice() { + return { + data: { + justifiedCheckpoint: chain.forkChoice.getJustifiedCheckpoint(), + finalizedCheckpoint: chain.forkChoice.getFinalizedCheckpoint(), + forkChoiceNodes: chain.forkChoice.getAllNodes().map((node) => ({ + slot: node.slot, + blockRoot: node.blockRoot, + parentRoot: node.parentRoot, + justifiedEpoch: node.justifiedEpoch, + finalizedEpoch: node.finalizedEpoch, + weight: node.weight, + validity: (() => { + switch (node.executionStatus) { + case ExecutionStatus.Valid: + return "valid"; + case ExecutionStatus.Invalid: + return "invalid"; + case ExecutionStatus.Syncing: + case ExecutionStatus.PreMerge: + return "optimistic"; + } + })(), + executionBlockHash: node.executionPayloadBlockHash ?? ZERO_HASH_HEX, + })), + }, + }; + }, + async getProtoArrayNodes() { const nodes = chain.forkChoice.getAllNodes().map((node) => ({ // if node has executionPayloadNumber, it will overwrite the below default diff --git a/packages/beacon-node/src/api/impl/errors.ts b/packages/beacon-node/src/api/impl/errors.ts index 848691f7cf6d..609f40f83a12 100644 --- a/packages/beacon-node/src/api/impl/errors.ts +++ b/packages/beacon-node/src/api/impl/errors.ts @@ -35,3 +35,16 @@ export class OnlySupportedByDVT extends ApiError { super(501, "Only supported by distributed validator middleware clients"); } } + +// Error thrown when processing multiple items failed - https://github.com/ethereum/beacon-APIs/blob/e7f7d70423b0abfe9d9f33b701be2ec03e44eb02/types/http.yaml#L175 +export class IndexedError extends ApiError { + failures: FailureList; + + constructor(message: string, failures: FailureList) { + super(400, message); + + this.failures = failures.sort((a, b) => a.index - b.index); + } +} + +export type FailureList = {index: number; message: string}[]; diff --git a/packages/beacon-node/src/api/impl/events/index.ts b/packages/beacon-node/src/api/impl/events/index.ts index 25659743ddf3..55a49fe0c7e3 100644 --- a/packages/beacon-node/src/api/impl/events/index.ts +++ b/packages/beacon-node/src/api/impl/events/index.ts @@ -10,11 +10,10 @@ export function getEventsApi({ const onAbortFns: (() => void)[] = []; for (const topic of topics) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: const handler = (data: any): void => { // TODO: What happens if this handler throws? Does it break the other chain.emitter listeners? - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment onEvent({type: topic, message: data}); }; diff --git a/packages/beacon-node/src/api/impl/lodestar/index.ts b/packages/beacon-node/src/api/impl/lodestar/index.ts index 0e8d2b1fa94b..f89959ad9da6 100644 --- a/packages/beacon-node/src/api/impl/lodestar/index.ts +++ b/packages/beacon-node/src/api/impl/lodestar/index.ts @@ -109,6 +109,7 @@ export function getLodestarApi({ async getBlockProcessorQueueItems() { return { + // biome-ignore lint/complexity/useLiteralKeys: The `blockProcessor` is a protected attribute data: (chain as BeaconChain)["blockProcessor"].jobQueue.getItems().map((item) => { const [blockInputs, opts] = item.args; return { @@ -173,6 +174,7 @@ export function getLodestarApi({ async dumpDbBucketKeys({bucket}) { for (const repo of Object.values(db) as IBeaconDb[keyof IBeaconDb][]) { if (repo instanceof Repository) { + // biome-ignore lint/complexity/useLiteralKeys: `bucket` is protected and `bucketId` is private if (String(repo["bucket"]) === bucket || repo["bucketId"] === bucket) { return {data: stringifyKeys(await repo.keys())}; } @@ -218,8 +220,7 @@ function stringifyKeys(keys: (Uint8Array | number | string)[]): string[] { return keys.map((key) => { if (key instanceof Uint8Array) { return toHex(key); - } else { - return `${key}`; } + return `${key}`; }); } diff --git a/packages/beacon-node/src/api/impl/node/index.ts b/packages/beacon-node/src/api/impl/node/index.ts index 370bba9b3c79..bd1fb85522de 100644 --- a/packages/beacon-node/src/api/impl/node/index.ts +++ b/packages/beacon-node/src/api/impl/node/index.ts @@ -76,10 +76,9 @@ export function getNodeApi( if (isSyncing || isOptimistic || elOffline) { // 206: Node is syncing but can serve incomplete data return {status: syncingStatus ?? routes.node.NodeHealth.SYNCING}; - } else { - // 200: Node is ready - return {status: routes.node.NodeHealth.READY}; } + // 200: Node is 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 diff --git a/packages/beacon-node/src/api/impl/node/utils.ts b/packages/beacon-node/src/api/impl/node/utils.ts index f26539eb0b36..27978a03ec39 100644 --- a/packages/beacon-node/src/api/impl/node/utils.ts +++ b/packages/beacon-node/src/api/impl/node/utils.ts @@ -44,6 +44,7 @@ function getPeerState(status: StreamStatus): routes.node.PeerState { case "closing": return "disconnecting"; case "closed": + return "disconnected"; default: return "disconnected"; } diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index a17c1418809e..2b558577c22c 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -1,14 +1,18 @@ +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import { CachedBeaconStateAllForks, computeStartSlotAtEpoch, + calculateCommitteeAssignments, proposerShufflingDecisionRoot, attesterShufflingDecisionRoot, getBlockRootAtSlot, computeEpochAtSlot, getCurrentSlot, beaconBlockToBlinded, + createCachedBeaconState, + loadState, } from "@lodestar/state-transition"; import { GENESIS_SLOT, @@ -40,9 +44,18 @@ import { BeaconBlock, BlockContents, BlindedBeaconBlock, + getValidatorStatus, } from "@lodestar/types"; import {ExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice"; -import {fromHex, toHex, resolveOrRacePromises, prettyWeiToEth, toRootHex} from "@lodestar/utils"; +import { + fromHex, + toHex, + resolveOrRacePromises, + prettyWeiToEth, + toRootHex, + TimeoutError, + formatWeiToEth, +} from "@lodestar/utils"; import { AttestationError, AttestationErrorCode, @@ -60,11 +73,12 @@ import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/va import {CommitteeSubscription} from "../../../network/subnets/index.js"; import {ApiModules} from "../types.js"; import {RegenCaller} from "../../../chain/regen/index.js"; -import {getValidatorStatus} from "../beacon/state/utils.js"; +import {getStateResponseWithRegen} from "../beacon/state/utils.js"; import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js"; import {SCHEDULER_LOOKAHEAD_FACTOR} from "../../../chain/prepareNextSlot.js"; import {ChainEvent, CheckpointHex, CommonBlockBody} from "../../../chain/index.js"; import {ApiOptions} from "../../options.js"; +import {NoBidReceived} from "../../../execution/builder/http.js"; import {getLodestarClientVersion} from "../../../util/metadata.js"; import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js"; @@ -84,7 +98,7 @@ import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProdu export const SYNC_TOLERANCE_EPOCHS = 1; /** - * Cutoff time to wait for execution and builder block production apis to resolve + * Cutoff time to wait from start of the slot for execution and builder block production apis to resolve * Post this time, race execution and builder to pick whatever resolves first * * Empirically the builder block resolves in ~1.5+ seconds, and execution should resolve <1 sec. @@ -109,6 +123,41 @@ type ProduceFullOrBlindedBlockOrContentsRes = {executionPayloadSource: ProducedB | (ProduceBlindedBlockRes & {executionPayloadBlinded: true}) ); +/** + * Engine block selection reasons tracked in metrics + */ +export enum EngineBlockSelectionReason { + BuilderDisabled = "builder_disabled", + BuilderError = "builder_error", + BuilderTimeout = "builder_timeout", + BuilderPending = "builder_pending", + BuilderNoBid = "builder_no_bid", + BuilderCensorship = "builder_censorship", + BlockValue = "block_value", + EnginePreferred = "engine_preferred", +} + +/** + * Builder block selection reasons tracked in metrics + */ +export enum BuilderBlockSelectionReason { + EngineDisabled = "engine_disabled", + EngineError = "engine_error", + EnginePending = "engine_pending", + BlockValue = "block_value", + BuilderPreferred = "builder_preferred", +} + +export type BlockSelectionResult = + | { + source: ProducedBlockSource.engine; + reason: EngineBlockSelectionReason; + } + | { + source: ProducedBlockSource.builder; + reason: BuilderBlockSelectionReason; + }; + /** * Server implementation for handling validator duties. * See `@lodestar/validator/src/api` for the client implementation). @@ -163,7 +212,9 @@ export function getValidatorApi( if (msToSlot > MAX_API_CLOCK_DISPARITY_MS) { throw Error(`Requested slot ${slot} is in the future`); - } else if (msToSlot > 0) { + } + + if (msToSlot > 0) { await chain.clock.waitForSlot(slot); } @@ -210,19 +261,19 @@ export function getValidatorApi( consensusBlockValue: prettyWeiToEth(consensusValue), blockTotalValue: prettyWeiToEth(totalValue), }; - } else if (source === ProducedBlockSource.builder) { + } + if (source === ProducedBlockSource.builder) { return { builderExecutionPayloadValue: prettyWeiToEth(executionValue), builderConsensusBlockValue: prettyWeiToEth(consensusValue), builderBlockTotalValue: prettyWeiToEth(totalValue), }; - } else { - return { - engineExecutionPayloadValue: prettyWeiToEth(executionValue), - engineConsensusBlockValue: prettyWeiToEth(consensusValue), - engineBlockTotalValue: prettyWeiToEth(totalValue), - }; } + return { + engineExecutionPayloadValue: prettyWeiToEth(executionValue), + engineConsensusBlockValue: prettyWeiToEth(consensusValue), + engineBlockTotalValue: prettyWeiToEth(totalValue), + }; } /** @@ -289,9 +340,9 @@ export function getValidatorApi( const headSlot = chain.forkChoice.getHead().slot; if (currentSlot - headSlot > SYNC_TOLERANCE_EPOCHS * SLOTS_PER_EPOCH) { throw new NodeIsSyncing(`headSlot ${headSlot} currentSlot ${currentSlot}`); - } else { - return; } + + return; } case SyncState.Synced: @@ -388,17 +439,13 @@ export function getValidatorApi( 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); parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot); } else { parentBlockRoot = inParentBlockRoot; } notOnOutOfRangeData(parentBlockRoot); - let timer; + let timer: undefined | ((opts: {source: ProducedBlockSource}) => number); try { timer = metrics?.blockProductionTime.startTimer(); const {block, executionPayloadValue, consensusBlockValue} = await chain.produceBlindedBlock({ @@ -413,6 +460,7 @@ export function getValidatorApi( metrics?.blockProductionSuccess.inc({source}); metrics?.blockProductionNumAggregated.observe({source}, block.body.attestations.length); + metrics?.blockProductionExecutionPayloadValue.observe({source}, Number(formatWeiToEth(executionPayloadValue))); logger.verbose("Produced blinded block", { slot, executionPayloadValue, @@ -458,17 +506,13 @@ export function getValidatorApi( 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); parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot); } else { parentBlockRoot = inParentBlockRoot; } notOnOutOfRangeData(parentBlockRoot); - let timer; + let timer: undefined | ((opts: {source: ProducedBlockSource}) => number); try { timer = metrics?.blockProductionTime.startTimer(); const {block, executionPayloadValue, consensusBlockValue, shouldOverrideBuilder} = await chain.produceBlock({ @@ -491,6 +535,7 @@ export function getValidatorApi( metrics?.blockProductionSuccess.inc({source}); metrics?.blockProductionNumAggregated.observe({source}, block.body.attestations.length); + metrics?.blockProductionExecutionPayloadValue.observe({source}, Number(formatWeiToEth(executionPayloadValue))); logger.verbose("Produced execution block", { slot, executionPayloadValue, @@ -514,9 +559,9 @@ export function getValidatorApi( consensusBlockValue, shouldOverrideBuilder, }; - } else { - return {data: block, version, executionPayloadValue, consensusBlockValue, shouldOverrideBuilder}; } + + return {data: block, version, executionPayloadValue, consensusBlockValue, shouldOverrideBuilder}; } finally { if (timer) timer({source}); } @@ -534,10 +579,6 @@ export function getValidatorApi( 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); notOnOutOfRangeData(parentBlockRoot); @@ -594,9 +635,12 @@ export function getValidatorApi( }); logger.debug("Produced common block body", loggerContext); + // Calculate cutoff time based on start of the slot + const cutoffMs = Math.max(0, BLOCK_PRODUCTION_RACE_CUTOFF_MS - Math.round(chain.clock.secFromSlot(slot) * 1000)); + logger.verbose("Block production race (builder vs execution) starting", { ...loggerContext, - cutoffMs: BLOCK_PRODUCTION_RACE_CUTOFF_MS, + cutoffMs, timeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS, }); @@ -642,7 +686,7 @@ export function getValidatorApi( : Promise.reject(new Error("Engine disabled")); const [builder, engine] = await resolveOrRacePromises([builderPromise, enginePromise], { - resolveTimeoutMs: BLOCK_PRODUCTION_RACE_CUTOFF_MS, + resolveTimeoutMs: cutoffMs, raceTimeoutMs: BLOCK_PRODUCTION_RACE_TIMEOUT_MS, signal: controller.signal, }); @@ -663,14 +707,21 @@ export function getValidatorApi( } if (builder.status === "rejected" && isBuilderEnabled) { - logger.warn( - "Builder failed to produce the block", - { + if (builder.reason instanceof NoBidReceived) { + logger.info("Builder did not provide a bid", { ...loggerContext, durationMs: builder.durationMs, - }, - builder.reason - ); + }); + } else { + logger.warn( + "Builder failed to produce the block", + { + ...loggerContext, + durationMs: builder.durationMs, + }, + builder.reason + ); + } } if (builder.status === "rejected" && engine.status === "rejected") { @@ -688,6 +739,11 @@ export function getValidatorApi( ...getBlockValueLogInfo(engine.value), }); + metrics?.blockProductionSelectionResults.inc({ + source: ProducedBlockSource.engine, + reason: EngineBlockSelectionReason.BuilderCensorship, + }); + return {...engine.value, executionPayloadBlinded: false, executionPayloadSource: ProducedBlockSource.engine}; } @@ -698,6 +754,16 @@ export function getValidatorApi( ...getBlockValueLogInfo(builder.value), }); + metrics?.blockProductionSelectionResults.inc({ + source: ProducedBlockSource.builder, + reason: + isEngineEnabled === false + ? BuilderBlockSelectionReason.EngineDisabled + : engine.status === "pending" + ? BuilderBlockSelectionReason.EnginePending + : BuilderBlockSelectionReason.EngineError, + }); + return {...builder.value, executionPayloadBlinded: true, executionPayloadSource: ProducedBlockSource.builder}; } @@ -708,16 +774,33 @@ export function getValidatorApi( ...getBlockValueLogInfo(engine.value), }); + metrics?.blockProductionSelectionResults.inc({ + source: ProducedBlockSource.engine, + reason: + isBuilderEnabled === false + ? EngineBlockSelectionReason.BuilderDisabled + : builder.status === "pending" + ? EngineBlockSelectionReason.BuilderPending + : builder.reason instanceof NoBidReceived + ? EngineBlockSelectionReason.BuilderNoBid + : builder.reason instanceof TimeoutError + ? EngineBlockSelectionReason.BuilderTimeout + : EngineBlockSelectionReason.BuilderError, + }); + return {...engine.value, executionPayloadBlinded: false, executionPayloadSource: ProducedBlockSource.engine}; } if (engine.status === "fulfilled" && builder.status === "fulfilled") { - const executionPayloadSource = selectBlockProductionSource({ + const result = selectBlockProductionSource({ builderBlockValue: builder.value.executionPayloadValue + builder.value.consensusBlockValue, engineBlockValue: engine.value.executionPayloadValue + engine.value.consensusBlockValue, builderBoostFactor, builderSelection, }); + const executionPayloadSource = result.source; + + metrics?.blockProductionSelectionResults.inc(result); logger.info(`Selected ${executionPayloadSource} block`, { ...loggerContext, @@ -733,13 +816,13 @@ export function getValidatorApi( executionPayloadBlinded: false, executionPayloadSource, }; - } else { - return { - ...builder.value, - executionPayloadBlinded: true, - executionPayloadSource, - }; } + + return { + ...builder.value, + executionPayloadBlinded: true, + executionPayloadSource, + }; } throw Error("Unreachable error occurred during the builder and execution block production"); @@ -764,25 +847,25 @@ export function getValidatorApi( if (opts.blindedLocal === true && ForkSeq[meta.version] >= ForkSeq.bellatrix) { if (meta.executionPayloadBlinded) { return {data, meta}; - } else { - if (isBlockContents(data)) { - const {block} = data; - const blindedBlock = beaconBlockToBlinded(config, block as BeaconBlock); - return { - data: blindedBlock, - meta: {...meta, executionPayloadBlinded: true}, - }; - } else { - const blindedBlock = beaconBlockToBlinded(config, data as BeaconBlock); - return { - data: blindedBlock, - meta: {...meta, executionPayloadBlinded: true}, - }; - } } - } else { - return {data, meta}; + + if (isBlockContents(data)) { + const {block} = data; + const blindedBlock = beaconBlockToBlinded(config, block as BeaconBlock); + return { + data: blindedBlock, + meta: {...meta, executionPayloadBlinded: true}, + }; + } + + const blindedBlock = beaconBlockToBlinded(config, data as BeaconBlock); + return { + data: blindedBlock, + meta: {...meta, executionPayloadBlinded: true}, + }; } + + return {data, meta}; }, async produceBlindedBlock({slot, randaoReveal, graffiti}) { @@ -795,12 +878,14 @@ export function getValidatorApi( const {block} = data; const blindedBlock = beaconBlockToBlinded(config, block as BeaconBlock); return {data: blindedBlock, meta: {version}}; - } else if (isBlindedBeaconBlock(data)) { + } + + if (isBlindedBeaconBlock(data)) { return {data, meta: {version}}; - } else { - const blindedBlock = beaconBlockToBlinded(config, data as BeaconBlock); - return {data: blindedBlock, meta: {version}}; } + + const blindedBlock = beaconBlockToBlinded(config, data as BeaconBlock); + return {data: blindedBlock, meta: {version}}; }, async produceAttestationData({committeeIndex, slot}) { @@ -899,15 +984,16 @@ export function getValidatorApi( async getProposerDuties({epoch}) { notWhileSyncing(); - // Early check that epoch is within [current_epoch, current_epoch + 1], or allow for pre-genesis + // Early check that epoch is no more than current_epoch + 1, or allow for pre-genesis const currentEpoch = currentEpochWithDisparity(); const nextEpoch = currentEpoch + 1; - if (currentEpoch >= 0 && epoch !== currentEpoch && epoch !== nextEpoch) { - throw Error(`Requested epoch ${epoch} must equal current ${currentEpoch} or next epoch ${nextEpoch}`); + if (currentEpoch >= 0 && epoch > nextEpoch) { + throw new ApiError(400, `Requested epoch ${epoch} must not be more than one epoch in the future`); } const head = chain.forkChoice.getHead(); let state: CachedBeaconStateAllForks | undefined = undefined; + const startSlot = computeStartSlotAtEpoch(epoch); const slotMs = config.SECONDS_PER_SLOT * 1000; const prepareNextSlotLookAheadMs = slotMs / SCHEDULER_LOOKAHEAD_FACTOR; const toNextEpochMs = msToNextEpoch(); @@ -925,21 +1011,65 @@ export function getValidatorApi( } if (!state) { - state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties); + if (epoch >= currentEpoch - 1) { + // Cached beacon state stores proposers for previous, current and next epoch. The + // requested epoch is within that range, we can use the head state at current epoch + state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.getDuties); + } else { + const res = await getStateResponseWithRegen(chain, startSlot); + + const stateViewDU = + res.state instanceof Uint8Array + ? loadState(config, chain.getHeadState(), res.state).state + : res.state.clone(); + + state = createCachedBeaconState( + stateViewDU, + { + config: chain.config, + // Not required to compute proposers + pubkey2index: new PubkeyIndexMap(), + index2pubkey: [], + }, + {skipSyncPubkeys: true, skipSyncCommitteeCache: true} + ); + + if (state.epochCtx.epoch !== epoch) { + throw Error(`Loaded state epoch ${state.epochCtx.epoch} does not match requested epoch ${epoch}`); + } + } } const stateEpoch = state.epochCtx.epoch; let indexes: ValidatorIndex[] = []; - if (epoch === stateEpoch) { - indexes = state.epochCtx.getBeaconProposers(); - } else if (epoch === stateEpoch + 1) { - // Requesting duties for next epoch is allow since they can be predicted with high probabilities. - // @see `epochCtx.getBeaconProposersNextEpoch` JSDocs for rationale. - indexes = state.epochCtx.getBeaconProposersNextEpoch(); - } else { - // Should never happen, epoch is checked to be in bounds above - throw Error(`Proposer duties for epoch ${epoch} not supported, current epoch ${stateEpoch}`); + switch (epoch) { + case stateEpoch: + indexes = state.epochCtx.getBeaconProposers(); + break; + + case stateEpoch + 1: + // make sure shuffling is calculated and ready for next call to calculate nextProposers + await chain.shufflingCache.get(state.epochCtx.nextEpoch, state.epochCtx.nextDecisionRoot); + // Requesting duties for next epoch is allowed since they can be predicted with high probabilities. + // @see `epochCtx.getBeaconProposersNextEpoch` JSDocs for rationale. + indexes = state.epochCtx.getBeaconProposersNextEpoch(); + break; + + case stateEpoch - 1: { + const indexesPrevEpoch = state.epochCtx.getBeaconProposersPrevEpoch(); + if (indexesPrevEpoch === null) { + // Should not happen as previous proposer duties should be initialized for head state + // and if we load state from `Uint8Array` it will always be the state of requested epoch + throw Error(`Proposer duties for previous epoch ${epoch} not yet initialized`); + } + indexes = indexesPrevEpoch; + break; + } + + default: + // Should never happen, epoch is checked to be in bounds above + throw Error(`Proposer duties for epoch ${epoch} not supported, current epoch ${stateEpoch}`); } // NOTE: this is the fastest way of getting compressed pubkeys. @@ -948,7 +1078,6 @@ export function getValidatorApi( // TODO: Add a flag to just send 0x00 as pubkeys since the Lodestar validator does not need them. const pubkeys = getPubkeysForIndices(state.validators, indexes); - const startSlot = computeStartSlotAtEpoch(epoch); const duties: routes.validator.ProposerDuty[] = []; for (let i = 0; i < SLOTS_PER_EPOCH; i++) { duties.push({slot: startSlot + i, validatorIndex: indexes[i], pubkey: pubkeys[i]}); @@ -995,7 +1124,15 @@ export function getValidatorApi( // Check that all validatorIndex belong to the state before calling getCommitteeAssignments() const pubkeys = getPubkeysForIndices(state.validators, indices); - const committeeAssignments = state.epochCtx.getCommitteeAssignments(epoch, indices); + const decisionRoot = state.epochCtx.getShufflingDecisionRoot(epoch); + const shuffling = await chain.shufflingCache.get(epoch, decisionRoot); + if (!shuffling) { + throw new ApiError( + 500, + `No shuffling found to calculate committee assignments for epoch: ${epoch} and decisionRoot: ${decisionRoot}` + ); + } + const committeeAssignments = calculateCommitteeAssignments(shuffling, indices); const duties: routes.validator.AttesterDuty[] = []; for (let i = 0, len = indices.length; i < len; i++) { const validatorIndex = indices[i]; @@ -1078,7 +1215,7 @@ export function getValidatorApi( await waitForSlot(slot); // Must never request for a future slot > currentSlot - const dataRootHex = toHex(attestationDataRoot); + const dataRootHex = toRootHex(attestationDataRoot); const aggregate = chain.attestationPool.getAggregate(slot, null, dataRootHex); const fork = chain.config.getForkName(slot); @@ -1138,7 +1275,6 @@ export function getValidatorApi( signedAggregateAndProofs.map(async (signedAggregateAndProof, i) => { try { // TODO: Validate in batch - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const validateFn = () => validateApiAggregateAndProof(fork, chain, signedAggregateAndProof); const {slot, beaconBlockRoot} = signedAggregateAndProof.message.aggregate.data; // when a validator is configured with multiple beacon node urls, this attestation may come from another beacon node @@ -1182,7 +1318,9 @@ export function getValidatorApi( if (errors.length > 1) { throw Error("Multiple errors on publishAggregateAndProofs\n" + errors.map((e) => e.message).join("\n")); - } else if (errors.length === 1) { + } + + if (errors.length === 1) { throw errors[0]; } }, @@ -1238,7 +1376,9 @@ export function getValidatorApi( if (errors.length > 1) { throw Error("Multiple errors on publishContributionAndProofs\n" + errors.map((e) => e.message).join("\n")); - } else if (errors.length === 1) { + } + + if (errors.length === 1) { throw errors[0]; } }, @@ -1350,12 +1490,11 @@ export function getValidatorApi( const filteredRegistrations = registrations.filter((registration) => { const {pubkey} = registration.message; const validatorIndex = headState.epochCtx.pubkey2index.get(pubkey); - if (validatorIndex === undefined) return false; + if (validatorIndex === null) return false; const validator = headState.validators.getReadonly(validatorIndex); const status = getValidatorStatus(validator, currentEpoch); return ( - status === "active" || status === "active_exiting" || status === "active_ongoing" || status === "active_slashed" || diff --git a/packages/beacon-node/src/api/impl/validator/utils.ts b/packages/beacon-node/src/api/impl/validator/utils.ts index 418e0a052787..36accf34cfda 100644 --- a/packages/beacon-node/src/api/impl/validator/utils.ts +++ b/packages/beacon-node/src/api/impl/validator/utils.ts @@ -3,6 +3,7 @@ import {ATTESTATION_SUBNET_COUNT} from "@lodestar/params"; import {routes} from "@lodestar/api"; import {BLSPubkey, CommitteeIndex, ProducedBlockSource, Slot, ValidatorIndex} from "@lodestar/types"; import {MAX_BUILDER_BOOST_FACTOR} from "@lodestar/validator"; +import {BlockSelectionResult, BuilderBlockSelectionReason, EngineBlockSelectionReason} from "./index.js"; export function computeSubnetForCommitteesAtSlot( slot: Slot, @@ -54,21 +55,31 @@ export function selectBlockProductionSource({ engineBlockValue: bigint; builderBlockValue: bigint; builderBoostFactor: bigint; -}): ProducedBlockSource { +}): BlockSelectionResult { switch (builderSelection) { case routes.validator.BuilderSelection.ExecutionAlways: case routes.validator.BuilderSelection.ExecutionOnly: - return ProducedBlockSource.engine; + return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.EnginePreferred}; case routes.validator.BuilderSelection.Default: - case routes.validator.BuilderSelection.MaxProfit: - return builderBoostFactor !== MAX_BUILDER_BOOST_FACTOR && - (builderBoostFactor === BigInt(0) || engineBlockValue >= (builderBlockValue * builderBoostFactor) / BigInt(100)) - ? ProducedBlockSource.engine - : ProducedBlockSource.builder; + case routes.validator.BuilderSelection.MaxProfit: { + if (builderBoostFactor === BigInt(0)) { + return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.EnginePreferred}; + } + + if (builderBoostFactor === MAX_BUILDER_BOOST_FACTOR) { + return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BuilderPreferred}; + } + + if (engineBlockValue >= (builderBlockValue * builderBoostFactor) / BigInt(100)) { + return {source: ProducedBlockSource.engine, reason: EngineBlockSelectionReason.BlockValue}; + } + + return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BlockValue}; + } case routes.validator.BuilderSelection.BuilderAlways: case routes.validator.BuilderSelection.BuilderOnly: - return ProducedBlockSource.builder; + return {source: ProducedBlockSource.builder, reason: BuilderBlockSelectionReason.BuilderPreferred}; } } diff --git a/packages/beacon-node/src/api/rest/activeSockets.ts b/packages/beacon-node/src/api/rest/activeSockets.ts index 9f1b0f1a78a3..e6a9aa382333 100644 --- a/packages/beacon-node/src/api/rest/activeSockets.ts +++ b/packages/beacon-node/src/api/rest/activeSockets.ts @@ -93,7 +93,7 @@ export class HttpActiveSocketsTracker { await waitFor(() => this.sockets.size === 0, { timeout: GRACEFUL_TERMINATION_TIMEOUT, }); - } catch { + } catch (_e) { // Ignore timeout error } finally { for (const socket of this.sockets) { diff --git a/packages/beacon-node/src/api/rest/base.ts b/packages/beacon-node/src/api/rest/base.ts index 5f191bf76beb..276583dc7281 100644 --- a/packages/beacon-node/src/api/rest/base.ts +++ b/packages/beacon-node/src/api/rest/base.ts @@ -5,7 +5,7 @@ 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"; +import {ApiError, FailureList, IndexedError, NodeIsSyncing} from "../impl/errors.js"; import {HttpActiveSocketsTracker, SocketMetrics} from "./activeSockets.js"; export type RestApiServerOpts = { @@ -15,6 +15,7 @@ export type RestApiServerOpts = { bearerToken?: string; headerLimit?: number; bodyLimit?: number; + stacktraces?: boolean; swaggerUI?: boolean; }; @@ -29,11 +30,31 @@ export type RestApiServerMetrics = SocketMetrics & { errors: Gauge<{operationId: string}>; }; +/** + * Error response body format as defined in beacon-api spec + * + * See https://github.com/ethereum/beacon-APIs/blob/v2.5.0/types/http.yaml + */ +type ErrorResponse = { + code: number; + message: string; + stacktraces?: string[]; +}; + +type IndexedErrorResponse = ErrorResponse & { + failures?: FailureList; +}; + /** * Error code used by Fastify if media type is not supported (415) */ const INVALID_MEDIA_TYPE_CODE = errorCodes.FST_ERR_CTP_INVALID_MEDIA_TYPE().code; +/** + * Error code used by Fastify if JSON schema validation failed + */ +const SCHEMA_VALIDATION_ERROR_CODE = errorCodes.FST_ERR_VALIDATION().code; + /** * REST API powered by `fastify` server. */ @@ -71,15 +92,38 @@ export class RestApiServer { // To parse our ApiError -> statusCode server.setErrorHandler((err, _req, res) => { + const stacktraces = opts.stacktraces ? err.stack?.split("\n") : undefined; if (err.validation) { - void res.status(400).send(err.validation); + const {instancePath, message} = err.validation[0]; + const payload: ErrorResponse = { + code: 400, + message: `${instancePath.substring(instancePath.lastIndexOf("/") + 1)} ${message}`, + stacktraces, + }; + void res.status(400).send(payload); + } else if (err instanceof IndexedError) { + const payload: IndexedErrorResponse = { + code: err.statusCode, + message: err.message, + failures: err.failures, + stacktraces, + }; + void res.status(err.statusCode).send(payload); } else { // Convert our custom ApiError into status code const statusCode = err instanceof ApiError ? err.statusCode : 500; - void res.status(statusCode).send(err); + const payload: ErrorResponse = {code: statusCode, message: err.message, stacktraces}; + void res.status(statusCode).send(payload); } }); + server.setNotFoundHandler((req, res) => { + const message = `Route ${req.raw.method}:${req.raw.url} not found`; + this.logger.warn(message); + const payload: ErrorResponse = {code: 404, message}; + void res.code(404).send(payload); + }); + if (opts.cors) { void server.register(fastifyCors, {origin: opts.cors}); } @@ -127,7 +171,7 @@ export class RestApiServer { const operationId = getOperationId(req); - if (err instanceof ApiError || err.code === INVALID_MEDIA_TYPE_CODE) { + if (err instanceof ApiError || [INVALID_MEDIA_TYPE_CODE, SCHEMA_VALIDATION_ERROR_CODE].includes(err.code)) { this.logger.warn(`Req ${req.id} ${operationId} failed`, {reason: err.message}); } else { this.logger.error(`Req ${req.id} ${operationId} error`, {}, err); diff --git a/packages/beacon-node/src/api/rest/index.ts b/packages/beacon-node/src/api/rest/index.ts index e27ed6bd9139..6beaf061588c 100644 --- a/packages/beacon-node/src/api/rest/index.ts +++ b/packages/beacon-node/src/api/rest/index.ts @@ -22,6 +22,7 @@ export const beaconRestApiServerOpts: BeaconRestApiServerOpts = { cors: "*", // beacon -> validator API is trusted, and for large amounts of keys the payload is multi-MB bodyLimit: 20 * 1024 * 1024, // 20MB for big block + blobs + stacktraces: false, }; export type BeaconRestApiServerModules = RestApiServerModules & { diff --git a/packages/beacon-node/src/api/rest/swaggerUI.ts b/packages/beacon-node/src/api/rest/swaggerUI.ts index c48f4e51860d..b4db3e43e6b1 100644 --- a/packages/beacon-node/src/api/rest/swaggerUI.ts +++ b/packages/beacon-node/src/api/rest/swaggerUI.ts @@ -41,15 +41,13 @@ async function getAsset(name: string): Promise { const path = await import("node:path"); const fs = await import("node:fs/promises"); const url = await import("node:url"); - // eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); return await fs.readFile(path.join(__dirname, "../../../../../assets/", name)); - } catch (e) { + } catch (_e) { return undefined; } } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export async function getFavicon() { const content = await getAsset("round-icon.ico"); if (!content) { @@ -67,7 +65,6 @@ export async function getFavicon() { ]; } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export async function getLogo() { const content = await getAsset("lodestar_icon_text_white.png"); if (!content) { diff --git a/packages/beacon-node/src/chain/archiver/archiveBlocks.ts b/packages/beacon-node/src/chain/archiver/archiveBlocks.ts index ed6dd5bfc091..8e1ac456579a 100644 --- a/packages/beacon-node/src/chain/archiver/archiveBlocks.ts +++ b/packages/beacon-node/src/chain/archiver/archiveBlocks.ts @@ -1,7 +1,6 @@ -import {fromHexString} from "@chainsafe/ssz"; import {Epoch, Slot, RootHex} from "@lodestar/types"; import {IForkChoice} from "@lodestar/fork-choice"; -import {Logger, toRootHex} from "@lodestar/utils"; +import {Logger, fromHex, toRootHex} from "@lodestar/utils"; import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {KeyValue} from "@lodestar/db"; @@ -48,7 +47,7 @@ export async function archiveBlocks( const finalizedCanonicalBlockRoots: BlockRootSlot[] = finalizedCanonicalBlocks.map((block) => ({ slot: block.slot, - root: fromHexString(block.blockRoot), + root: fromHex(block.blockRoot), })); if (finalizedCanonicalBlockRoots.length > 0) { @@ -68,7 +67,7 @@ export async function archiveBlocks( // deleteNonCanonicalBlocks // loop through forkchoice single time - const nonCanonicalBlockRoots = finalizedNonCanonicalBlocks.map((summary) => fromHexString(summary.blockRoot)); + const nonCanonicalBlockRoots = finalizedNonCanonicalBlocks.map((summary) => fromHex(summary.blockRoot)); if (nonCanonicalBlockRoots.length > 0) { await db.block.batchDelete(nonCanonicalBlockRoots); logger.verbose("Deleted non canonical blocks from hot DB", { @@ -144,7 +143,7 @@ async function migrateBlocksFromHotToColdDb(db: IBeaconDb, blocks: BlockRootSlot value: blockBuffer, slot: block.slot, blockRoot: block.root, - // TODO: Benchmark if faster to slice Buffer or fromHexString() + // TODO: Benchmark if faster to slice Buffer or fromHex() parentRoot: getParentRootFromSignedBlock(blockBuffer), }; }) diff --git a/packages/beacon-node/src/chain/archiver/archiver.ts b/packages/beacon-node/src/chain/archiver/archiver.ts new file mode 100644 index 000000000000..2d79f584ea79 --- /dev/null +++ b/packages/beacon-node/src/chain/archiver/archiver.ts @@ -0,0 +1,166 @@ +import {Logger} from "@lodestar/utils"; +import {CheckpointWithHex} from "@lodestar/fork-choice"; +import {IBeaconDb} from "../../db/index.js"; +import {JobItemQueue} from "../../util/queue/index.js"; +import {IBeaconChain} from "../interface.js"; +import {ChainEvent} from "../emitter.js"; +import {Metrics} from "../../metrics/metrics.js"; +import {FrequencyStateArchiveStrategy} from "./strategies/frequencyStateArchiveStrategy.js"; +import {archiveBlocks} from "./archiveBlocks.js"; +import {StateArchiveMode, ArchiverOpts, StateArchiveStrategy} from "./interface.js"; + +export const DEFAULT_STATE_ARCHIVE_MODE = StateArchiveMode.Frequency; + +export const PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN = 256; + +/** + * Used for running tasks that depends on some events or are executed + * periodically. + */ +export class Archiver { + private stateArchiveMode: StateArchiveMode; + private jobQueue: JobItemQueue<[CheckpointWithHex], void>; + + private prevFinalized: CheckpointWithHex; + private readonly statesArchiverStrategy: StateArchiveStrategy; + private archiveBlobEpochs?: number; + + constructor( + private readonly db: IBeaconDb, + private readonly chain: IBeaconChain, + private readonly logger: Logger, + signal: AbortSignal, + opts: ArchiverOpts, + private readonly metrics?: Metrics | null + ) { + if (opts.stateArchiveMode === StateArchiveMode.Frequency) { + this.statesArchiverStrategy = new FrequencyStateArchiveStrategy(chain.regen, db, logger, opts, chain.bufferPool); + } else { + throw new Error(`State archive strategy "${opts.stateArchiveMode}" currently not supported.`); + } + + this.stateArchiveMode = opts.stateArchiveMode; + this.archiveBlobEpochs = opts.archiveBlobEpochs; + this.prevFinalized = chain.forkChoice.getFinalizedCheckpoint(); + this.jobQueue = new JobItemQueue<[CheckpointWithHex], void>(this.processFinalizedCheckpoint, { + maxLength: PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN, + signal, + }); + + if (!opts.disableArchiveOnCheckpoint) { + this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint); + this.chain.emitter.on(ChainEvent.checkpoint, this.onCheckpoint); + + signal.addEventListener( + "abort", + () => { + this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint); + this.chain.emitter.off(ChainEvent.checkpoint, this.onCheckpoint); + }, + {once: true} + ); + } + } + + /** Archive latest finalized state */ + async persistToDisk(): Promise { + return this.statesArchiverStrategy.maybeArchiveState(this.chain.forkChoice.getFinalizedCheckpoint()); + } + + private onFinalizedCheckpoint = async (finalized: CheckpointWithHex): Promise => { + return this.jobQueue.push(finalized); + }; + + private onCheckpoint = (): void => { + const headStateRoot = this.chain.forkChoice.getHead().stateRoot; + this.chain.regen.pruneOnCheckpoint( + this.chain.forkChoice.getFinalizedCheckpoint().epoch, + this.chain.forkChoice.getJustifiedCheckpoint().epoch, + headStateRoot + ); + + this.statesArchiverStrategy.onCheckpoint(headStateRoot, this.metrics).catch((err) => { + this.logger.error("Error during state archive", {stateArchiveMode: this.stateArchiveMode}, err); + }); + }; + + private processFinalizedCheckpoint = async (finalized: CheckpointWithHex): Promise => { + try { + const finalizedEpoch = finalized.epoch; + this.logger.verbose("Start processing finalized checkpoint", {epoch: finalizedEpoch, rootHex: finalized.rootHex}); + await archiveBlocks( + this.chain.config, + this.db, + this.chain.forkChoice, + this.chain.lightClientServer, + this.logger, + finalized, + this.chain.clock.currentEpoch, + this.archiveBlobEpochs + ); + this.prevFinalized = finalized; + + await this.statesArchiverStrategy.onFinalizedCheckpoint(finalized, this.metrics); + + // should be after ArchiveBlocksTask to handle restart cleanly + await this.statesArchiverStrategy.maybeArchiveState(finalized, this.metrics); + + this.chain.regen.pruneOnFinalized(finalizedEpoch); + + // tasks rely on extended fork choice + const prunedBlocks = this.chain.forkChoice.prune(finalized.rootHex); + await this.updateBackfillRange(finalized); + + this.logger.verbose("Finish processing finalized checkpoint", { + epoch: finalizedEpoch, + rootHex: finalized.rootHex, + prunedBlocks: prunedBlocks.length, + }); + } catch (e) { + this.logger.error("Error processing finalized checkpoint", {epoch: finalized.epoch}, e as Error); + } + }; + + /** + * Backfill sync relies on verified connected ranges (which are represented as key,value + * with a verified jump from a key back to value). Since the node could have progressed + * ahead from, we need to save the forward progress of this node as another backfill + * range entry, that backfill sync will use to jump back if this node is restarted + * for any reason. + * The current backfill has its own backfill entry from anchor slot to last backfilled + * slot. And this would create the entry from the current finalized slot to the anchor + * slot. + */ + private updateBackfillRange = async (finalized: CheckpointWithHex): Promise => { + try { + // Mark the sequence in backfill db from finalized block's slot till anchor slot as + // filled. + const finalizedBlockFC = this.chain.forkChoice.getBlockHex(finalized.rootHex); + if (finalizedBlockFC && finalizedBlockFC.slot > this.chain.anchorStateLatestBlockSlot) { + await this.db.backfilledRanges.put(finalizedBlockFC.slot, this.chain.anchorStateLatestBlockSlot); + + // Clear previously marked sequence till anchorStateLatestBlockSlot, without + // touching backfill sync process sequence which are at + // <=anchorStateLatestBlockSlot i.e. clear >anchorStateLatestBlockSlot + // and < currentSlot + const filteredSeqs = await this.db.backfilledRanges.entries({ + gt: this.chain.anchorStateLatestBlockSlot, + lt: finalizedBlockFC.slot, + }); + this.logger.debug("updated backfilledRanges", { + key: finalizedBlockFC.slot, + value: this.chain.anchorStateLatestBlockSlot, + }); + if (filteredSeqs.length > 0) { + await this.db.backfilledRanges.batchDelete(filteredSeqs.map((entry) => entry.key)); + this.logger.debug( + `Forward Sync - cleaned up backfilledRanges between ${finalizedBlockFC.slot},${this.chain.anchorStateLatestBlockSlot}`, + {seqs: JSON.stringify(filteredSeqs)} + ); + } + } + } catch (e) { + this.logger.error("Error updating backfilledRanges on finalization", {epoch: finalized.epoch}, e as Error); + } + }; +} diff --git a/packages/beacon-node/src/chain/archiver/index.ts b/packages/beacon-node/src/chain/archiver/index.ts index 294c2281e19b..dbcafbe458a6 100644 --- a/packages/beacon-node/src/chain/archiver/index.ts +++ b/packages/beacon-node/src/chain/archiver/index.ts @@ -1,168 +1,2 @@ -import {Logger} from "@lodestar/utils"; -import {CheckpointWithHex} from "@lodestar/fork-choice"; -import {IBeaconDb} from "../../db/index.js"; -import {JobItemQueue} from "../../util/queue/index.js"; -import {IBeaconChain} from "../interface.js"; -import {ChainEvent} from "../emitter.js"; -import {StatesArchiver, StatesArchiverOpts} from "./archiveStates.js"; -import {archiveBlocks} from "./archiveBlocks.js"; - -const PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN = 256; - -export type ArchiverOpts = StatesArchiverOpts & { - disableArchiveOnCheckpoint?: boolean; - archiveBlobEpochs?: number; -}; - -type ProposalStats = { - total: number; - finalized: number; - orphaned: number; - missed: number; -}; - -export type FinalizedStats = { - allValidators: ProposalStats; - attachedValidators: ProposalStats; - finalizedCanonicalCheckpointsCount: number; - finalizedFoundCheckpointsInStateCache: number; - finalizedAttachedValidatorsCount: number; -}; - -/** - * Used for running tasks that depends on some events or are executed - * periodically. - */ -export class Archiver { - private jobQueue: JobItemQueue<[CheckpointWithHex], void>; - - private prevFinalized: CheckpointWithHex; - private readonly statesArchiver: StatesArchiver; - private archiveBlobEpochs?: number; - - constructor( - private readonly db: IBeaconDb, - private readonly chain: IBeaconChain, - private readonly logger: Logger, - signal: AbortSignal, - opts: ArchiverOpts - ) { - this.archiveBlobEpochs = opts.archiveBlobEpochs; - this.statesArchiver = new StatesArchiver(chain.regen, db, logger, opts, chain.bufferPool); - this.prevFinalized = chain.forkChoice.getFinalizedCheckpoint(); - this.jobQueue = new JobItemQueue<[CheckpointWithHex], void>(this.processFinalizedCheckpoint, { - maxLength: PROCESS_FINALIZED_CHECKPOINT_QUEUE_LEN, - signal, - }); - - if (!opts.disableArchiveOnCheckpoint) { - this.chain.emitter.on(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint); - this.chain.emitter.on(ChainEvent.checkpoint, this.onCheckpoint); - - signal.addEventListener( - "abort", - () => { - this.chain.emitter.off(ChainEvent.forkChoiceFinalized, this.onFinalizedCheckpoint); - this.chain.emitter.off(ChainEvent.checkpoint, this.onCheckpoint); - }, - {once: true} - ); - } - } - - /** Archive latest finalized state */ - async persistToDisk(): Promise { - await this.statesArchiver.archiveState(this.chain.forkChoice.getFinalizedCheckpoint()); - } - - private onFinalizedCheckpoint = async (finalized: CheckpointWithHex): Promise => { - return this.jobQueue.push(finalized); - }; - - private onCheckpoint = (): void => { - const headStateRoot = this.chain.forkChoice.getHead().stateRoot; - this.chain.regen.pruneOnCheckpoint( - this.chain.forkChoice.getFinalizedCheckpoint().epoch, - this.chain.forkChoice.getJustifiedCheckpoint().epoch, - headStateRoot - ); - }; - - private processFinalizedCheckpoint = async (finalized: CheckpointWithHex): Promise => { - try { - const finalizedEpoch = finalized.epoch; - this.logger.verbose("Start processing finalized checkpoint", {epoch: finalizedEpoch, rootHex: finalized.rootHex}); - await archiveBlocks( - this.chain.config, - this.db, - this.chain.forkChoice, - this.chain.lightClientServer, - this.logger, - finalized, - this.chain.clock.currentEpoch, - this.archiveBlobEpochs - ); - this.prevFinalized = finalized; - - // should be after ArchiveBlocksTask to handle restart cleanly - await this.statesArchiver.maybeArchiveState(finalized); - - this.chain.regen.pruneOnFinalized(finalizedEpoch); - - // tasks rely on extended fork choice - const prunedBlocks = this.chain.forkChoice.prune(finalized.rootHex); - await this.updateBackfillRange(finalized); - - this.logger.verbose("Finish processing finalized checkpoint", { - epoch: finalizedEpoch, - rootHex: finalized.rootHex, - prunedBlocks: prunedBlocks.length, - }); - } catch (e) { - this.logger.error("Error processing finalized checkpoint", {epoch: finalized.epoch}, e as Error); - } - }; - - /** - * Backfill sync relies on verified connected ranges (which are represented as key,value - * with a verified jump from a key back to value). Since the node could have progressed - * ahead from, we need to save the forward progress of this node as another backfill - * range entry, that backfill sync will use to jump back if this node is restarted - * for any reason. - * The current backfill has its own backfill entry from anchor slot to last backfilled - * slot. And this would create the entry from the current finalized slot to the anchor - * slot. - */ - private updateBackfillRange = async (finalized: CheckpointWithHex): Promise => { - try { - // Mark the sequence in backfill db from finalized block's slot till anchor slot as - // filled. - const finalizedBlockFC = this.chain.forkChoice.getBlockHex(finalized.rootHex); - if (finalizedBlockFC && finalizedBlockFC.slot > this.chain.anchorStateLatestBlockSlot) { - await this.db.backfilledRanges.put(finalizedBlockFC.slot, this.chain.anchorStateLatestBlockSlot); - - // Clear previously marked sequence till anchorStateLatestBlockSlot, without - // touching backfill sync process sequence which are at - // <=anchorStateLatestBlockSlot i.e. clear >anchorStateLatestBlockSlot - // and < currentSlot - const filteredSeqs = await this.db.backfilledRanges.entries({ - gt: this.chain.anchorStateLatestBlockSlot, - lt: finalizedBlockFC.slot, - }); - this.logger.debug("updated backfilledRanges", { - key: finalizedBlockFC.slot, - value: this.chain.anchorStateLatestBlockSlot, - }); - if (filteredSeqs.length > 0) { - await this.db.backfilledRanges.batchDelete(filteredSeqs.map((entry) => entry.key)); - this.logger.debug( - `Forward Sync - cleaned up backfilledRanges between ${finalizedBlockFC.slot},${this.chain.anchorStateLatestBlockSlot}`, - {seqs: JSON.stringify(filteredSeqs)} - ); - } - } - } catch (e) { - this.logger.error("Error updating backfilledRanges on finalization", {epoch: finalized.epoch}, e as Error); - } - }; -} +export * from "./archiver.js"; +export * from "./interface.js"; diff --git a/packages/beacon-node/src/chain/archiver/interface.ts b/packages/beacon-node/src/chain/archiver/interface.ts new file mode 100644 index 000000000000..48c930c78cd3 --- /dev/null +++ b/packages/beacon-node/src/chain/archiver/interface.ts @@ -0,0 +1,47 @@ +import {CheckpointWithHex} from "@lodestar/fork-choice"; +import {Metrics} from "../../metrics/metrics.js"; +import {RootHex} from "@lodestar/types"; + +export enum StateArchiveMode { + Frequency = "frequency", + // New strategy to be implemented + // WIP: https://github.com/ChainSafe/lodestar/pull/7005 + // Differential = "diff", +} + +export interface StatesArchiverOpts { + /** + * Minimum number of epochs between archived states + */ + archiveStateEpochFrequency: number; + /** + * Strategy to store archive states + */ + stateArchiveMode: StateArchiveMode; +} + +export type ArchiverOpts = StatesArchiverOpts & { + disableArchiveOnCheckpoint?: boolean; + archiveBlobEpochs?: number; +}; + +export type ProposalStats = { + total: number; + finalized: number; + orphaned: number; + missed: number; +}; + +export type FinalizedStats = { + allValidators: ProposalStats; + attachedValidators: ProposalStats; + finalizedCanonicalCheckpointsCount: number; + finalizedFoundCheckpointsInStateCache: number; + finalizedAttachedValidatorsCount: number; +}; + +export interface StateArchiveStrategy { + onCheckpoint(stateRoot: RootHex, metrics?: Metrics | null): Promise; + onFinalizedCheckpoint(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise; + maybeArchiveState(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise; +} diff --git a/packages/beacon-node/src/chain/archiver/archiveStates.ts b/packages/beacon-node/src/chain/archiver/strategies/frequencyStateArchiveStrategy.ts similarity index 77% rename from packages/beacon-node/src/chain/archiver/archiveStates.ts rename to packages/beacon-node/src/chain/archiver/strategies/frequencyStateArchiveStrategy.ts index 53f2033d80e8..a701da5b22ec 100644 --- a/packages/beacon-node/src/chain/archiver/archiveStates.ts +++ b/packages/beacon-node/src/chain/archiver/strategies/frequencyStateArchiveStrategy.ts @@ -1,33 +1,28 @@ import {Logger} from "@lodestar/utils"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {Slot, Epoch} from "@lodestar/types"; +import {Slot, Epoch, RootHex} from "@lodestar/types"; import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {CheckpointWithHex} from "@lodestar/fork-choice"; -import {IBeaconDb} from "../../db/index.js"; -import {IStateRegenerator} from "../regen/interface.js"; -import {getStateSlotFromBytes} from "../../util/multifork.js"; -import {serializeState} from "../serializeState.js"; -import {AllocSource, BufferPool} from "../../util/bufferPool.js"; +import {IBeaconDb} from "../../../db/index.js"; +import {IStateRegenerator} from "../../regen/interface.js"; +import {getStateSlotFromBytes} from "../../../util/multifork.js"; +import {serializeState} from "../../serializeState.js"; +import {AllocSource, BufferPool} from "../../../util/bufferPool.js"; +import {Metrics} from "../../../metrics/metrics.js"; +import {StateArchiveStrategy, StatesArchiverOpts} from "../interface.js"; /** * Minimum number of epochs between single temp archived states * These states will be pruned once a new state is persisted */ -const PERSIST_TEMP_STATE_EVERY_EPOCHS = 32; - -export interface StatesArchiverOpts { - /** - * Minimum number of epochs between archived states - */ - archiveStateEpochFrequency: number; -} +export const PERSIST_TEMP_STATE_EVERY_EPOCHS = 32; /** * Archives finalized states from active bucket to archive bucket. * * Only the new finalized state is stored to disk */ -export class StatesArchiver { +export class FrequencyStateArchiveStrategy implements StateArchiveStrategy { constructor( private readonly regen: IStateRegenerator, private readonly db: IBeaconDb, @@ -36,6 +31,9 @@ export class StatesArchiver { private readonly bufferPool?: BufferPool | null ) {} + async onFinalizedCheckpoint(_finalized: CheckpointWithHex, _metrics?: Metrics | null): Promise {} + async onCheckpoint(_stateRoot: RootHex, _metrics?: Metrics | null): Promise {} + /** * Persist states every some epochs to * - Minimize disk space, storing the least states possible @@ -48,13 +46,13 @@ export class StatesArchiver { * epoch - 1024*2 epoch - 1024 epoch - 32 epoch * ``` */ - async maybeArchiveState(finalized: CheckpointWithHex): Promise { + async maybeArchiveState(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise { const lastStoredSlot = await this.db.stateArchive.lastKey(); const lastStoredEpoch = computeEpochAtSlot(lastStoredSlot ?? 0); const {archiveStateEpochFrequency} = this.opts; if (finalized.epoch - lastStoredEpoch >= Math.min(PERSIST_TEMP_STATE_EVERY_EPOCHS, archiveStateEpochFrequency)) { - await this.archiveState(finalized); + await this.archiveState(finalized, metrics); // Only check the current and previous intervals const minEpoch = Math.max( @@ -86,7 +84,7 @@ export class StatesArchiver { * Archives finalized states from active bucket to archive bucket. * Only the new finalized state is stored to disk */ - async archiveState(finalized: CheckpointWithHex): Promise { + private async archiveState(finalized: CheckpointWithHex, metrics?: Metrics | null): Promise { // starting from Mar 2024, the finalized state could be from disk or in memory const finalizedStateOrBytes = await this.regen.getCheckpointStateOrBytes(finalized); const {rootHex} = finalized; @@ -99,10 +97,14 @@ export class StatesArchiver { this.logger.verbose("Archived finalized state bytes", {epoch: finalized.epoch, slot, root: rootHex}); } else { // serialize state using BufferPool if provided + const timer = metrics?.stateSerializeDuration.startTimer({source: AllocSource.ARCHIVE_STATE}); await serializeState( finalizedStateOrBytes, AllocSource.ARCHIVE_STATE, - (stateBytes) => this.db.stateArchive.putBinary(finalizedStateOrBytes.slot, stateBytes), + (stateBytes) => { + timer?.(); + return this.db.stateArchive.putBinary(finalizedStateOrBytes.slot, stateBytes); + }, this.bufferPool ); // don't delete states before the finalized state, auto-prune will take care of it diff --git a/packages/beacon-node/src/chain/balancesCache.ts b/packages/beacon-node/src/chain/balancesCache.ts index 50a86b31b6c8..d29a6998f600 100644 --- a/packages/beacon-node/src/chain/balancesCache.ts +++ b/packages/beacon-node/src/chain/balancesCache.ts @@ -35,7 +35,7 @@ export class CheckpointBalancesCache { const epochBoundaryRoot = epochBoundarySlot === state.slot ? blockRootHex : toRootHex(getBlockRootAtSlot(state, epochBoundarySlot)); - const index = this.items.findIndex((item) => item.epoch === epoch && item.rootHex == epochBoundaryRoot); + const index = this.items.findIndex((item) => item.epoch === epoch && item.rootHex === epochBoundaryRoot); if (index === -1) { if (this.items.length === MAX_BALANCE_CACHE_SIZE) { this.items.shift(); diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index de5ecf607d95..596f01f391a4 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -1,4 +1,3 @@ -import {toHexString} from "@chainsafe/ssz"; import {capella, ssz, altair, BeaconBlock} from "@lodestar/types"; import {ForkLightClient, ForkSeq, INTERVALS_PER_SLOT, MAX_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params"; import { @@ -10,7 +9,7 @@ import { } from "@lodestar/state-transition"; import {routes} from "@lodestar/api"; import {ForkChoiceError, ForkChoiceErrorCode, EpochDifference, AncestorStatus} from "@lodestar/fork-choice"; -import {isErrorAborted, toRootHex} from "@lodestar/utils"; +import {isErrorAborted, toHex, toRootHex} from "@lodestar/utils"; import {ZERO_HASH_HEX} from "../../constants/index.js"; import {toCheckpointHex} from "../stateCache/index.js"; import {isOptimisticBlock} from "../../util/forkChoice.js"; @@ -20,6 +19,7 @@ import {ChainEvent, ReorgEventData} from "../emitter.js"; import {REPROCESS_MIN_TIME_TO_NEXT_SLOT_SEC} from "../reprocess.js"; import type {BeaconChain} from "../chain.js"; import {callInNextEventLoop} from "../../util/eventLoop.js"; +import {ForkchoiceCaller} from "../forkChoice/index.js"; import {FullyVerifiedBlock, ImportBlockOpts, AttestationImportOpt, BlockInputType} from "./types.js"; import {getCheckpointFromState} from "./utils/checkpoint.js"; import {writeBlockInputToDb} from "./writeBlockInputToDb.js"; @@ -65,7 +65,6 @@ export async function importBlock( const blockRootHex = toRootHex(blockRoot); const currentEpoch = computeEpochAtSlot(this.forkChoice.getTime()); const blockEpoch = computeEpochAtSlot(blockSlot); - const parentEpoch = computeEpochAtSlot(parentBlockSlot); const prevFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch; const blockDelaySec = (fullyVerifiedBlock.seenTimestampSec - postState.genesisTime) % this.config.SECONDS_PER_SLOT; const recvToValLatency = Date.now() / 1000 - (opts.seenTimestampSec ?? Date.now() / 1000); @@ -210,7 +209,7 @@ export async function importBlock( // 5. Compute head. If new head, immediately stateCache.setHeadState() const oldHead = this.forkChoice.getHead(); - const newHead = this.recomputeForkChoiceHead(); + const newHead = this.recomputeForkChoiceHead(ForkchoiceCaller.importBlock); const currFinalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch; if (newHead.blockRoot !== oldHead.blockRoot) { @@ -336,12 +335,6 @@ export async function importBlock( this.logger.verbose("After importBlock caching postState without SSZ cache", {slot: postState.slot}); } - if (parentEpoch < blockEpoch) { - // current epoch and previous epoch are likely cached in previous states - this.shufflingCache.processState(postState, postState.epochCtx.nextShuffling.epoch); - this.logger.verbose("Processed shuffling for next epoch", {parentEpoch, blockEpoch, slot: blockSlot}); - } - if (blockSlot % SLOTS_PER_EPOCH === 0) { // Cache state to preserve epoch transition work const checkpointState = postState; @@ -433,8 +426,8 @@ export async function importBlock( blockRoot: blockRootHex, slot: blockSlot, index, - kzgCommitment: toHexString(kzgCommitment), - versionedHash: toHexString(kzgCommitmentToVersionedHash(kzgCommitment)), + kzgCommitment: toHex(kzgCommitment), + versionedHash: toHex(kzgCommitmentToVersionedHash(kzgCommitment)), }); } } diff --git a/packages/beacon-node/src/chain/blocks/index.ts b/packages/beacon-node/src/chain/blocks/index.ts index a7a2ced2ad7a..f64e4b1f3813 100644 --- a/packages/beacon-node/src/chain/blocks/index.ts +++ b/packages/beacon-node/src/chain/blocks/index.ts @@ -53,7 +53,9 @@ export async function processBlocks( ): Promise { if (blocks.length === 0) { return; // TODO: or throw? - } else if (blocks.length > 1) { + } + + if (blocks.length > 1) { assertLinearChainSegment(this.config, blocks); } @@ -161,11 +163,13 @@ export async function processBlocks( function getBlockError(e: unknown, block: SignedBeaconBlock): BlockError { if (e instanceof BlockError) { return e; - } else if (e instanceof Error) { + } + + if (e instanceof Error) { const blockError = new BlockError(block, {code: BlockErrorCode.BEACON_CHAIN_ERROR, error: e}); blockError.stack = e.stack; return blockError; - } else { - return new BlockError(block, {code: BlockErrorCode.BEACON_CHAIN_ERROR, error: e as Error}); } + + return new BlockError(block, {code: BlockErrorCode.BEACON_CHAIN_ERROR, error: e as Error}); } diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksDataAvailability.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksDataAvailability.ts index 8393c91063de..98bfae2c1ce3 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksDataAvailability.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksDataAvailability.ts @@ -78,12 +78,12 @@ async function maybeValidateBlobs( case BlockInputType.outOfRangeData: return {dataAvailabilityStatus: DataAvailabilityStatus.OutOfRange, availableBlockInput: blockInput}; + // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here case BlockInputType.availableData: if (opts.validBlobSidecars === BlobSidecarValidation.Full) { return {dataAvailabilityStatus: DataAvailabilityStatus.Available, availableBlockInput: blockInput}; } - // eslint-disable-next-line no-fallthrough case BlockInputType.dataPromise: { // run full validation const {block} = blockInput; @@ -136,7 +136,7 @@ async function raceWithCutoff( try { await Promise.race([availabilityPromise, cutoffTimeout]); - } catch (e) { + } catch (_e) { // throw unavailable so that the unknownblock/blobs can be triggered to pull the block throw new BlockError(block, {code: BlockErrorCode.DATA_UNAVAILABLE}); } diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts index 284d4c0976ee..e641ff9ae6d9 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksExecutionPayloads.ts @@ -5,7 +5,7 @@ import { isMergeTransitionBlock as isMergeTransitionBlockFn, isExecutionEnabled, } from "@lodestar/state-transition"; -import {bellatrix, Slot, deneb, SignedBeaconBlock} from "@lodestar/types"; +import {bellatrix, Slot, deneb, SignedBeaconBlock, electra} from "@lodestar/types"; import { IForkChoice, assertValidTerminalPowBlock, @@ -302,6 +302,8 @@ export async function verifyBlockExecutionPayload( ? (block.message.body as deneb.BeaconBlockBody).blobKzgCommitments.map(kzgCommitmentToVersionedHash) : undefined; const parentBlockRoot = ForkSeq[fork] >= ForkSeq.deneb ? block.message.parentRoot : undefined; + const executionRequests = + ForkSeq[fork] >= ForkSeq.electra ? (block.message.body as electra.BeaconBlockBody).executionRequests : undefined; const logCtx = {slot: block.message.slot, executionBlock: executionPayloadEnabled.blockNumber}; chain.logger.debug("Call engine api newPayload", logCtx); @@ -309,7 +311,8 @@ export async function verifyBlockExecutionPayload( fork, executionPayloadEnabled, versionedHashes, - parentBlockRoot + parentBlockRoot, + executionRequests ); chain.logger.debug("Receive engine api newPayload result", {...logCtx, status: execResult.status}); diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts index 573dfa52ce8b..22b20a55fb67 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksSanityChecks.ts @@ -45,9 +45,8 @@ export function verifyBlocksSanityChecks( if (blockSlot === 0) { if (opts.ignoreIfKnown) { continue; - } else { - throw new BlockError(block, {code: BlockErrorCode.GENESIS_BLOCK}); } + throw new BlockError(block, {code: BlockErrorCode.GENESIS_BLOCK}); } // Not finalized slot @@ -56,9 +55,8 @@ export function verifyBlocksSanityChecks( if (blockSlot <= finalizedSlot) { if (opts.ignoreIfFinalized) { continue; - } else { - throw new BlockError(block, {code: BlockErrorCode.WOULD_REVERT_FINALIZED_SLOT, blockSlot, finalizedSlot}); } + throw new BlockError(block, {code: BlockErrorCode.WOULD_REVERT_FINALIZED_SLOT, blockSlot, finalizedSlot}); } let parentBlockSlot: Slot; @@ -71,10 +69,9 @@ export function verifyBlocksSanityChecks( parentBlock = chain.forkChoice.getBlockHex(parentRoot); if (!parentBlock) { throw new BlockError(block, {code: BlockErrorCode.PARENT_UNKNOWN, parentRoot}); - } else { - // Parent is known to the fork-choice - parentBlockSlot = parentBlock.slot; } + // Parent is known to the fork-choice + parentBlockSlot = parentBlock.slot; } // Block not in the future, also checks for infinity @@ -89,9 +86,9 @@ export function verifyBlocksSanityChecks( if (chain.forkChoice.hasBlockHex(blockHash)) { if (opts.ignoreIfKnown) { continue; - } else { - throw new BlockError(block, {code: BlockErrorCode.ALREADY_KNOWN, root: blockHash}); } + + throw new BlockError(block, {code: BlockErrorCode.ALREADY_KNOWN, root: blockHash}); } // Block is relevant diff --git a/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts b/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts index 89cf7ddc7556..010c6a1fabc1 100644 --- a/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts +++ b/packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts @@ -31,7 +31,7 @@ export async function writeBlockInputToDb(this: BeaconChain, blocksInput: BlockI if (blockInput.type === BlockInputType.availableData || blockInput.type === BlockInputType.dataPromise) { const blobSidecars = - blockInput.type == BlockInputType.availableData + blockInput.type === BlockInputType.availableData ? blockInput.blockData.blobs : // At this point of import blobs are available and can be safely awaited (await blockInput.cachedData.availabilityPromise).blobs; diff --git a/packages/beacon-node/src/chain/bls/multithread/index.ts b/packages/beacon-node/src/chain/bls/multithread/index.ts index 3725fa4bcb1c..fc8ea7534ad7 100644 --- a/packages/beacon-node/src/chain/bls/multithread/index.ts +++ b/packages/beacon-node/src/chain/bls/multithread/index.ts @@ -3,9 +3,8 @@ import path from "node:path"; import {spawn, Worker} from "@chainsafe/threads"; // `threads` library creates self global variable which breaks `timeout-abort-controller` https://github.com/jacobheun/timeout-abort-controller/issues/9 // Don't add an eslint disable here as a reminder that this has to be fixed eventually -// eslint-disable-next-line // @ts-ignore -// eslint-disable-next-line +// biome-ignore lint/suspicious/noGlobalAssign: self = undefined; import {PublicKey} from "@chainsafe/blst"; import {Logger} from "@lodestar/utils"; @@ -314,7 +313,8 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier { this.workers[0].status.code === WorkerStatusCode.initializationError && this.workers.every((worker) => worker.status.code === WorkerStatusCode.initializationError) ) { - return job.reject(this.workers[0].status.error); + job.reject(this.workers[0].status.error); + return; } // Append batchable sets to `bufferedJobs`, starting a timeout to push them into `jobs`. @@ -394,7 +394,7 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier { try { // Note: This can throw, must be handled per-job. // Pubkey and signature aggregation is defered here - workReq = jobItemWorkReq(job, this.metrics); + workReq = await jobItemWorkReq(job, this.metrics); } catch (e) { this.metrics?.blsThreadPool.errorAggregateSignatureSetsCount.inc({type: job.type}); diff --git a/packages/beacon-node/src/chain/bls/multithread/jobItem.ts b/packages/beacon-node/src/chain/bls/multithread/jobItem.ts index 035d56e56df2..63591af5ee77 100644 --- a/packages/beacon-node/src/chain/bls/multithread/jobItem.ts +++ b/packages/beacon-node/src/chain/bls/multithread/jobItem.ts @@ -1,4 +1,4 @@ -import {PublicKey, aggregateWithRandomness} from "@chainsafe/blst"; +import {PublicKey, asyncAggregateWithRandomness} from "@chainsafe/blst"; import {ISignatureSet, SignatureSetType} from "@lodestar/state-transition"; import {VerifySignatureOpts} from "../interface.js"; import {getAggregatedPubkey} from "../utils.js"; @@ -48,7 +48,7 @@ export function jobItemSigSets(job: JobQueueItem): number { * Prepare BlsWorkReq from JobQueueItem * WARNING: May throw with untrusted user input */ -export function jobItemWorkReq(job: JobQueueItem, metrics: Metrics | null): BlsWorkReq { +export async function jobItemWorkReq(job: JobQueueItem, metrics: Metrics | null): Promise { switch (job.type) { case JobQueueItemType.default: return { @@ -61,17 +61,9 @@ export function jobItemWorkReq(job: JobQueueItem, metrics: Metrics | null): BlsW })), }; case JobQueueItemType.sameMessage: { - // This is slow code on main thread (mainly signature deserialization + group check). - // Ideally it can be taken off-thread, but in the mean time, keep track of total time spent here. - // As of July 2024, for a node subscribing to all subnets, with 1 signature per validator per epoch, - // it takes around 2.02 min to perform this operation for a single epoch. - // cpu profile on main thread has 250s idle so this only works until we reach 3M validators - // However, for normal node with only 2 to 7 subnet subscriptions per epoch this works until 27M validators - // and not a problem in the near future - // this is monitored on v1.21.0 https://github.com/ChainSafe/lodestar/pull/6894/files#r1687359225 - const timer = metrics?.blsThreadPool.aggregateWithRandomnessMainThreadDuration.startTimer(); - const {pk, sig} = aggregateWithRandomness(job.sets.map((set) => ({pk: set.publicKey, sig: set.signature}))); - timer?.(); + const {pk, sig} = await asyncAggregateWithRandomness( + job.sets.map((set) => ({pk: set.publicKey, sig: set.signature})) + ); return { opts: job.opts, diff --git a/packages/beacon-node/src/chain/bls/multithread/poolSize.ts b/packages/beacon-node/src/chain/bls/multithread/poolSize.ts index 9397f97849cd..2c683e191fd6 100644 --- a/packages/beacon-node/src/chain/bls/multithread/poolSize.ts +++ b/packages/beacon-node/src/chain/bls/multithread/poolSize.ts @@ -6,7 +6,7 @@ try { } else { defaultPoolSize = (await import("node:os")).availableParallelism(); } -} catch (e) { +} catch (_e) { defaultPoolSize = 8; } diff --git a/packages/beacon-node/src/chain/bls/multithread/worker.ts b/packages/beacon-node/src/chain/bls/multithread/worker.ts index 9073920df19c..d2794db2e6bf 100644 --- a/packages/beacon-node/src/chain/bls/multithread/worker.ts +++ b/packages/beacon-node/src/chain/bls/multithread/worker.ts @@ -75,7 +75,7 @@ function verifyManySignatureSets(workReqArr: BlsWorkReq[]): BlsWorkResult { // Re-verify all sigs nonBatchableSets.push(...batchableChunk); } - } catch (e) { + } catch (_e) { // TODO: Ignore this error expecting that the same error will happen when re-verifying the set individually // It's not ideal but '@chainsafe/blst' may throw errors on some conditions batchRetries++; diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 8dbb49798538..144b73f0c01d 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -1,5 +1,6 @@ import path from "node:path"; -import {CompositeTypeAny, fromHexString, TreeView, Type} from "@chainsafe/ssz"; +import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import { BeaconStateAllForks, CachedBeaconStateAllForks, @@ -10,9 +11,9 @@ import { getEffectiveBalanceIncrementsZeroInactive, isCachedBeaconState, Index2PubkeyCache, - PubkeyIndexMap, EpochShuffling, computeEndSlotAtEpoch, + computeAnchorCheckpoint, } from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; import { @@ -35,7 +36,7 @@ import { } from "@lodestar/types"; import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice"; import {ProcessShutdownCallback} from "@lodestar/validator"; -import {Logger, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils"; +import {Logger, fromHex, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toRootHex} from "@lodestar/utils"; import {ForkSeq, GENESIS_SLOT, SLOTS_PER_EPOCH} from "@lodestar/params"; import {GENESIS_EPOCH, ZERO_HASH} from "../constants/index.js"; @@ -59,8 +60,7 @@ import { } from "./interface.js"; import {IChainOptions} from "./options.js"; import {QueuedStateRegenerator, RegenCaller} from "./regen/index.js"; -import {initializeForkChoice} from "./forkChoice/index.js"; -import {computeAnchorCheckpoint} from "./initState.js"; +import {ForkchoiceCaller, initializeForkChoice} from "./forkChoice/index.js"; import {IBlsVerifier, BlsSingleThreadVerifier, BlsMultiThreadWorkerPool} from "./bls/index.js"; import { SeenAttesters, @@ -77,7 +77,7 @@ import { OpPool, } from "./opPools/index.js"; import {LightClientServer} from "./lightClient/index.js"; -import {Archiver} from "./archiver/index.js"; +import {Archiver} from "./archiver/archiver.js"; import {PrepareNextSlotScheduler} from "./prepareNextSlot.js"; import {ReprocessController} from "./reprocess.js"; import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js"; @@ -246,7 +246,6 @@ export class BeaconChain implements IBeaconChain { this.beaconProposerCache = new BeaconProposerCache(opts); this.checkpointBalancesCache = new CheckpointBalancesCache(); - this.shufflingCache = new ShufflingCache(metrics, this.opts); // Restore state caches // anchorState may already by a CachedBeaconState. If so, don't create the cache again, since deserializing all @@ -261,9 +260,21 @@ export class BeaconChain implements IBeaconChain { pubkey2index: new PubkeyIndexMap(), index2pubkey: [], }); - this.shufflingCache.processState(cachedState, cachedState.epochCtx.previousShuffling.epoch); - this.shufflingCache.processState(cachedState, cachedState.epochCtx.currentShuffling.epoch); - this.shufflingCache.processState(cachedState, cachedState.epochCtx.nextShuffling.epoch); + + this.shufflingCache = cachedState.epochCtx.shufflingCache = new ShufflingCache(metrics, logger, this.opts, [ + { + shuffling: cachedState.epochCtx.previousShuffling, + decisionRoot: cachedState.epochCtx.previousDecisionRoot, + }, + { + shuffling: cachedState.epochCtx.currentShuffling, + decisionRoot: cachedState.epochCtx.currentDecisionRoot, + }, + { + shuffling: cachedState.epochCtx.nextShuffling, + decisionRoot: cachedState.epochCtx.nextDecisionRoot, + }, + ]); // Persist single global instance of state caches this.pubkey2index = cachedState.epochCtx.pubkey2index; @@ -334,7 +345,7 @@ export class BeaconChain implements IBeaconChain { this.bls = bls; this.emitter = emitter; - this.archiver = new Archiver(db, this, logger, signal, opts); + this.archiver = new Archiver(db, this, logger, signal, opts, metrics); // always run PrepareNextSlotScheduler except for fork_choice spec tests if (!opts?.disablePrepareNextSlot) { new PrepareNextSlotScheduler(this, this.config, metrics, this.logger, signal); @@ -453,23 +464,23 @@ export class BeaconChain implements IBeaconChain { executionOptimistic: isOptimisticBlock(block), finalized: slot === finalizedBlock.slot && finalizedBlock.slot !== GENESIS_SLOT, }; - } else { - // Just check if state is already in the cache. If it's not dialed to the correct slot, - // do not bother in advancing the state. restApiCanTriggerRegen == false means do no work - const block = this.forkChoice.getCanonicalBlockAtSlot(slot); - if (!block) { - return null; - } + } - const state = this.regen.getStateSync(block.stateRoot); - return ( - state && { - state, - executionOptimistic: isOptimisticBlock(block), - finalized: slot === finalizedBlock.slot && finalizedBlock.slot !== GENESIS_SLOT, - } - ); + // Just check if state is already in the cache. If it's not dialed to the correct slot, + // do not bother in advancing the state. restApiCanTriggerRegen == false means do no work + const block = this.forkChoice.getCanonicalBlockAtSlot(slot); + if (!block) { + return null; } + + const state = this.regen.getStateSync(block.stateRoot); + return ( + state && { + state, + executionOptimistic: isOptimisticBlock(block), + finalized: slot === finalizedBlock.slot && finalizedBlock.slot !== GENESIS_SLOT, + } + ); } async getHistoricalStateBySlot( @@ -521,7 +532,7 @@ export class BeaconChain implements IBeaconChain { }; } - const data = await this.db.stateArchive.getByRoot(fromHexString(stateRoot)); + const data = await this.db.stateArchive.getByRoot(fromHex(stateRoot)); return data && {state: data, executionOptimistic: false, finalized: true}; } @@ -568,7 +579,7 @@ export class BeaconChain implements IBeaconChain { // Unfinalized slot, attempt to find in fork-choice const block = this.forkChoice.getCanonicalBlockAtSlot(slot); if (block) { - const data = await this.db.block.get(fromHexString(block.blockRoot)); + const data = await this.db.block.get(fromHex(block.blockRoot)); if (data) { return {block: data, executionOptimistic: isOptimisticBlock(block), finalized: false}; } @@ -587,7 +598,7 @@ export class BeaconChain implements IBeaconChain { ): 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)); + const data = await this.db.block.get(fromHex(root)); if (data) { return {block: data, executionOptimistic: isOptimisticBlock(block), finalized: false}; } @@ -595,7 +606,7 @@ export class BeaconChain implements IBeaconChain { // TODO: Add a lock to the archiver to have deterministic behavior on where are blocks } - const data = await this.db.blockArchive.getByRoot(fromHexString(root)); + const data = await this.db.blockArchive.getByRoot(fromHex(root)); return data && {block: data, executionOptimistic: false, finalized: true}; } @@ -768,14 +779,14 @@ export class BeaconChain implements IBeaconChain { finalizedRoot: finalizedCheckpoint.epoch === GENESIS_EPOCH ? ZERO_HASH : finalizedCheckpoint.root, finalizedEpoch: finalizedCheckpoint.epoch, // TODO: PERFORMANCE: Memoize to prevent re-computing every time - headRoot: fromHexString(head.blockRoot), + headRoot: fromHex(head.blockRoot), headSlot: head.slot, }; } - recomputeForkChoiceHead(): ProtoBlock { + recomputeForkChoiceHead(caller: ForkchoiceCaller): ProtoBlock { this.metrics?.forkChoice.requests.inc(); - const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.recomputeForkChoiceHead}); + const timer = this.metrics?.forkChoice.findHead.startTimer({caller}); try { return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetCanonicialHead}).head; @@ -789,7 +800,7 @@ export class BeaconChain implements IBeaconChain { predictProposerHead(slot: Slot): ProtoBlock { this.metrics?.forkChoice.requests.inc(); - const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.predictProposerHead}); + const timer = this.metrics?.forkChoice.findHead.startTimer({caller: FindHeadFnName.predictProposerHead}); try { return this.forkChoice.updateAndGetHead({mode: UpdateHeadOpt.GetPredictedProposerHead, slot}).head; @@ -803,7 +814,7 @@ export class BeaconChain implements IBeaconChain { getProposerHead(slot: Slot): ProtoBlock { this.metrics?.forkChoice.requests.inc(); - const timer = this.metrics?.forkChoice.findHead.startTimer({entrypoint: FindHeadFnName.getProposerHead}); + const timer = this.metrics?.forkChoice.findHead.startTimer({caller: FindHeadFnName.getProposerHead}); const secFromSlot = this.clock.secFromSlot(slot); try { @@ -902,8 +913,8 @@ export class BeaconChain implements IBeaconChain { state = await this.regen.getState(attHeadBlock.stateRoot, regenCaller); } - // resolve the promise to unblock other calls of the same epoch and dependent root - return this.shufflingCache.processState(state, attEpoch); + // should always be the current epoch of the active context so no need to await a result from the ShufflingCache + return state.epochCtx.getShufflingAtEpoch(attEpoch); } /** @@ -920,28 +931,27 @@ export class BeaconChain implements IBeaconChain { const effectiveBalances = this.checkpointBalancesCache.get(checkpoint); if (effectiveBalances) { return effectiveBalances; - } else { - // not expected, need metrics - this.metrics?.balancesCache.misses.inc(); - this.logger.debug("checkpointBalances cache miss", { - epoch: checkpoint.epoch, - root: checkpoint.rootHex, - }); - - const {state, stateId, shouldWarn} = this.closestJustifiedBalancesStateToCheckpoint(checkpoint, blockState); - this.metrics?.balancesCache.closestStateResult.inc({stateId}); - if (shouldWarn) { - this.logger.warn("currentJustifiedCheckpoint state not avail, using closest state", { - checkpointEpoch: checkpoint.epoch, - checkpointRoot: checkpoint.rootHex, - stateId, - stateSlot: state.slot, - stateRoot: toRootHex(state.hashTreeRoot()), - }); - } + } + // not expected, need metrics + this.metrics?.balancesCache.misses.inc(); + this.logger.debug("checkpointBalances cache miss", { + epoch: checkpoint.epoch, + root: checkpoint.rootHex, + }); - return getEffectiveBalanceIncrementsZeroInactive(state); + const {state, stateId, shouldWarn} = this.closestJustifiedBalancesStateToCheckpoint(checkpoint, blockState); + this.metrics?.balancesCache.closestStateResult.inc({stateId}); + if (shouldWarn) { + this.logger.warn("currentJustifiedCheckpoint state not avail, using closest state", { + checkpointEpoch: checkpoint.epoch, + checkpointRoot: checkpoint.rootHex, + stateId, + stateSlot: state.slot, + stateRoot: toRootHex(state.hashTreeRoot()), + }); } + + return getEffectiveBalanceIncrementsZeroInactive(state); } /** @@ -1049,8 +1059,8 @@ export class BeaconChain implements IBeaconChain { if (this.forkChoice.irrecoverableError) { this.processShutdownCallback(this.forkChoice.irrecoverableError); } - this.forkChoice.updateTime(slot); + this.forkChoice.updateTime(slot); this.metrics?.clockSlot.set(slot); this.attestationPool.prune(slot); @@ -1120,7 +1130,7 @@ export class BeaconChain implements IBeaconChain { // TODO: Improve using regen here const {blockRoot, stateRoot, slot} = this.forkChoice.getHead(); const headState = this.regen.getStateSync(stateRoot); - const headBlock = await this.db.block.get(fromHexString(blockRoot)); + const headBlock = await this.db.block.get(fromHex(blockRoot)); if (headBlock == null) { throw Error(`Head block ${slot} ${headBlock} is not available in database`); } diff --git a/packages/beacon-node/src/chain/emitter.ts b/packages/beacon-node/src/chain/emitter.ts index 9c3ea4a9bf52..10b6455e48f5 100644 --- a/packages/beacon-node/src/chain/emitter.ts +++ b/packages/beacon-node/src/chain/emitter.ts @@ -1,4 +1,4 @@ -import {EventEmitter} from "events"; +import {EventEmitter} from "node:events"; import {StrictEventEmitter} from "strict-event-emitter-types"; import {routes} from "@lodestar/api"; diff --git a/packages/beacon-node/src/chain/forkChoice/index.ts b/packages/beacon-node/src/chain/forkChoice/index.ts index d57b7f86cb98..839975de4e26 100644 --- a/packages/beacon-node/src/chain/forkChoice/index.ts +++ b/packages/beacon-node/src/chain/forkChoice/index.ts @@ -14,10 +14,10 @@ import { getEffectiveBalanceIncrementsZeroInactive, isExecutionStateType, isMergeTransitionComplete, + computeAnchorCheckpoint, } from "@lodestar/state-transition"; import {Logger, toRootHex} from "@lodestar/utils"; -import {computeAnchorCheckpoint} from "../initState.js"; import {ChainEventEmitter} from "../emitter.js"; import {ChainEvent} from "../emitter.js"; import {GENESIS_SLOT} from "../../constants/index.js"; @@ -27,6 +27,11 @@ export type ForkChoiceOpts = RawForkChoiceOpts & { forkchoiceConstructor?: typeof ForkChoice; }; +export enum ForkchoiceCaller { + prepareNextSlot = "prepare_next_slot", + importBlock = "import_block", +} + /** * Fork Choice extended with a ChainEventEmitter */ diff --git a/packages/beacon-node/src/chain/genesis/genesis.ts b/packages/beacon-node/src/chain/genesis/genesis.ts index 979476c69530..0c46f920d614 100644 --- a/packages/beacon-node/src/chain/genesis/genesis.ts +++ b/packages/beacon-node/src/chain/genesis/genesis.ts @@ -124,9 +124,9 @@ export class GenesisBuilder implements IGenesisBuilder { depositTree: this.depositTree, block, }; - } else { - this.throttledLog(`Waiting for min genesis time ${block.timestamp} / ${this.config.MIN_GENESIS_TIME}`); } + + this.throttledLog(`Waiting for min genesis time ${block.timestamp} / ${this.config.MIN_GENESIS_TIME}`); } throw Error("depositsStream stopped without a valid genesis state"); @@ -147,11 +147,11 @@ export class GenesisBuilder implements IGenesisBuilder { if (this.activatedValidatorCount >= this.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT) { this.logger.info("Found enough genesis validators", {blockNumber}); return blockNumber; - } else { - this.throttledLog( - `Found ${this.state.validators.length} / ${this.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT} validators to genesis` - ); } + + this.throttledLog( + `Found ${this.state.validators.length} / ${this.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT} validators to genesis` + ); } throw Error("depositsStream stopped without a valid genesis state"); diff --git a/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts b/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts index ada4f3c284d7..a9f254dea3f2 100644 --- a/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts +++ b/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts @@ -1,9 +1,9 @@ +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import { BeaconStateAllForks, CachedBeaconStateAllForks, DataAvailableStatus, ExecutionPayloadStatus, - PubkeyIndexMap, createCachedBeaconState, stateTransition, } from "@lodestar/state-transition"; @@ -102,6 +102,10 @@ export async function getHistoricalState( metrics?.stateTransitionBlocks.observe(blockCount); transitionTimer?.(); + if (state.slot !== slot) { + throw Error(`Failed to generate historical state for slot ${slot}`); + } + const serializeTimer = metrics?.stateSerializationTime.startTimer(); const stateBytes = state.serialize(); serializeTimer?.(); diff --git a/packages/beacon-node/src/chain/historicalState/worker.ts b/packages/beacon-node/src/chain/historicalState/worker.ts index a07207cac5f5..2ea673d0faff 100644 --- a/packages/beacon-node/src/chain/historicalState/worker.ts +++ b/packages/beacon-node/src/chain/historicalState/worker.ts @@ -1,13 +1,9 @@ import worker from "node:worker_threads"; import {Transfer, expose} from "@chainsafe/threads/worker"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {createBeaconConfig, chainConfigFromJson} from "@lodestar/config"; import {getNodeLogger} from "@lodestar/logger/node"; -import { - EpochTransitionStep, - PubkeyIndexMap, - StateCloneSource, - StateHashTreeRootSource, -} from "@lodestar/state-transition"; +import {EpochTransitionStep, StateCloneSource, StateHashTreeRootSource} from "@lodestar/state-transition"; import {LevelDbController} from "@lodestar/db"; import {RegistryMetricCreator, collectNodeJSMetrics} from "../../metrics/index.js"; import {JobFnQueue} from "../../util/queue/fnQueue.js"; diff --git a/packages/beacon-node/src/chain/initState.ts b/packages/beacon-node/src/chain/initState.ts index e413bdff0f7d..2883cdc8388e 100644 --- a/packages/beacon-node/src/chain/initState.ts +++ b/packages/beacon-node/src/chain/initState.ts @@ -1,15 +1,13 @@ import { - blockToHeader, computeEpochAtSlot, BeaconStateAllForks, CachedBeaconStateAllForks, - computeCheckpointEpochAtStateSlot, computeStartSlotAtEpoch, } from "@lodestar/state-transition"; -import {SignedBeaconBlock, phase0, ssz} from "@lodestar/types"; +import {SignedBeaconBlock} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {Logger, toHex, toRootHex} from "@lodestar/utils"; -import {GENESIS_SLOT, ZERO_HASH} from "../constants/index.js"; +import {GENESIS_SLOT} from "../constants/index.js"; import {IBeaconDb} from "../db/index.js"; import {Eth1Provider} from "../eth1/index.js"; import {Metrics} from "../metrics/index.js"; @@ -134,7 +132,7 @@ export async function initStateFromEth1({ * Restore the latest beacon state from db */ export async function initStateFromDb( - config: ChainForkConfig, + _config: ChainForkConfig, db: IBeaconDb, logger: Logger ): Promise { @@ -204,35 +202,3 @@ export function initBeaconMetrics(metrics: Metrics, state: BeaconStateAllForks): metrics.currentJustifiedEpoch.set(state.currentJustifiedCheckpoint.epoch); metrics.finalizedEpoch.set(state.finalizedCheckpoint.epoch); } - -export function computeAnchorCheckpoint( - config: ChainForkConfig, - anchorState: BeaconStateAllForks -): {checkpoint: phase0.Checkpoint; blockHeader: phase0.BeaconBlockHeader} { - let blockHeader; - let root; - const blockTypes = config.getForkTypes(anchorState.latestBlockHeader.slot); - - if (anchorState.latestBlockHeader.slot === GENESIS_SLOT) { - const block = blockTypes.BeaconBlock.defaultValue(); - block.stateRoot = anchorState.hashTreeRoot(); - blockHeader = blockToHeader(config, block); - root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader); - } else { - blockHeader = ssz.phase0.BeaconBlockHeader.clone(anchorState.latestBlockHeader); - if (ssz.Root.equals(blockHeader.stateRoot, ZERO_HASH)) { - blockHeader.stateRoot = anchorState.hashTreeRoot(); - } - root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader); - } - - return { - checkpoint: { - root, - // the checkpoint epoch = computeEpochAtSlot(anchorState.slot) + 1 if slot is not at epoch boundary - // this is similar to a process_slots() call - epoch: computeCheckpointEpochAtStateSlot(anchorState.slot), - }, - blockHeader, - }; -} diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 5185662eaa4f..3b44ffd594ae 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -1,4 +1,5 @@ import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import { UintNum64, Root, @@ -21,7 +22,6 @@ import { CachedBeaconStateAllForks, EpochShuffling, Index2PubkeyCache, - PubkeyIndexMap, } from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; import {Logger} from "@lodestar/utils"; @@ -58,6 +58,7 @@ import {ShufflingCache} from "./shufflingCache.js"; import {BlockRewards} from "./rewards/blockRewards.js"; import {AttestationsRewards} from "./rewards/attestationsRewards.js"; import {SyncCommitteeRewards} from "./rewards/syncCommitteeRewards.js"; +import {ForkchoiceCaller} from "./forkChoice/index.js"; export {BlockType, type AssembledBlockType}; export {type ProposerPreparationData}; @@ -204,7 +205,7 @@ export interface IBeaconChain { getStatus(): phase0.Status; - recomputeForkChoiceHead(): ProtoBlock; + recomputeForkChoiceHead(caller: ForkchoiceCaller): 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; diff --git a/packages/beacon-node/src/chain/lightClient/index.ts b/packages/beacon-node/src/chain/lightClient/index.ts index 30567b5d79b8..0a41ea059e73 100644 --- a/packages/beacon-node/src/chain/lightClient/index.ts +++ b/packages/beacon-node/src/chain/lightClient/index.ts @@ -225,7 +225,7 @@ export class LightClientServer { this.zero = { // Assign the hightest fork's default value because it can always be typecasted down to correct fork finalizedHeader: sszTypesFor(highestFork(forkLightClient)).LightClientHeader.defaultValue(), - finalityBranch: ssz.altair.LightClientUpdate.fields["finalityBranch"].defaultValue(), + finalityBranch: ssz.altair.LightClientUpdate.fields.finalityBranch.defaultValue(), }; if (metrics) { @@ -630,12 +630,12 @@ export class LightClientServer { ? await this.getFinalizedHeader(attestedData.finalizedCheckpoint.root) : null; - let isFinalized, finalityBranch, finalizedHeader; + let isFinalized: boolean, finalityBranch: Uint8Array[], finalizedHeader: LightClientHeader; if ( attestedData.isFinalized && finalizedHeaderAttested && - computeSyncPeriodAtSlot(finalizedHeaderAttested.beacon.slot) == syncPeriod + computeSyncPeriodAtSlot(finalizedHeaderAttested.beacon.slot) === syncPeriod ) { isFinalized = true; finalityBranch = attestedData.finalityBranch; @@ -741,7 +741,7 @@ export function blockToLightClientHeader(fork: ForkName, block: BeaconBlock b.notSeenAttesterCount - a.notSeenAttesterCount).slice(0, maxAttestation); } + + return attestations.sort((a, b) => b.notSeenAttesterCount - a.notSeenAttesterCount).slice(0, maxAttestation); } /** Get attestations for API. */ @@ -636,45 +636,43 @@ export function getNotSeenValidatorsFn(state: CachedBeaconStateAllForks): GetNot } // altair and future forks - else { - // Get attestations to be included in an altair block. - // Attestations are sorted by inclusion distance then number of attesters. - // Attestations should pass the validation when processing attestations in state-transition. - // check for altair block already - const altairState = state as CachedBeaconStateAltair; - const previousParticipation = altairState.previousEpochParticipation.getAll(); - const currentParticipation = altairState.currentEpochParticipation.getAll(); - const stateEpoch = computeEpochAtSlot(state.slot); - // this function could be called multiple times with same slot + committeeIndex - const cachedNotSeenValidators = new Map>(); - - return (epoch: Epoch, slot: Slot, committeeIndex: number) => { - const participationStatus = - epoch === stateEpoch ? currentParticipation : epoch === stateEpoch - 1 ? previousParticipation : null; - - if (participationStatus === null) { - return null; - } - const cacheKey = slot + "_" + committeeIndex; - let notSeenAttestingIndices = cachedNotSeenValidators.get(cacheKey); - if (notSeenAttestingIndices != null) { - // if all validators are seen then return null, we don't need to check for any attestations of same committee again - return notSeenAttestingIndices.size === 0 ? null : notSeenAttestingIndices; - } - - const committee = state.epochCtx.getBeaconCommittee(slot, committeeIndex); - notSeenAttestingIndices = new Set(); - for (const [i, validatorIndex] of committee.entries()) { - // no need to check flagIsTimelySource as if validator is not seen, it's participation status is 0 - if (participationStatus[validatorIndex] === 0) { - notSeenAttestingIndices.add(i); - } - } - cachedNotSeenValidators.set(cacheKey, notSeenAttestingIndices); + // Get attestations to be included in an altair block. + // Attestations are sorted by inclusion distance then number of attesters. + // Attestations should pass the validation when processing attestations in state-transition. + // check for altair block already + const altairState = state as CachedBeaconStateAltair; + const previousParticipation = altairState.previousEpochParticipation.getAll(); + const currentParticipation = altairState.currentEpochParticipation.getAll(); + const stateEpoch = computeEpochAtSlot(state.slot); + // this function could be called multiple times with same slot + committeeIndex + const cachedNotSeenValidators = new Map>(); + + return (epoch: Epoch, slot: Slot, committeeIndex: number) => { + const participationStatus = + epoch === stateEpoch ? currentParticipation : epoch === stateEpoch - 1 ? previousParticipation : null; + + if (participationStatus === null) { + return null; + } + const cacheKey = slot + "_" + committeeIndex; + let notSeenAttestingIndices = cachedNotSeenValidators.get(cacheKey); + if (notSeenAttestingIndices != null) { // if all validators are seen then return null, we don't need to check for any attestations of same committee again return notSeenAttestingIndices.size === 0 ? null : notSeenAttestingIndices; - }; - } + } + + const committee = state.epochCtx.getBeaconCommittee(slot, committeeIndex); + notSeenAttestingIndices = new Set(); + for (const [i, validatorIndex] of committee.entries()) { + // no need to check flagIsTimelySource as if validator is not seen, it's participation status is 0 + if (participationStatus[validatorIndex] === 0) { + notSeenAttestingIndices.add(i); + } + } + cachedNotSeenValidators.set(cacheKey, notSeenAttestingIndices); + // if all validators are seen then return null, we don't need to check for any attestations of same committee again + return notSeenAttestingIndices.size === 0 ? null : notSeenAttestingIndices; + }; } export function extractParticipationPhase0( @@ -716,7 +714,7 @@ export function getValidateAttestationDataFn( const stateEpoch = state.epochCtx.epoch; return (attData: phase0.AttestationData) => { const targetEpoch = attData.target.epoch; - let justifiedCheckpoint; + let justifiedCheckpoint: phase0.Checkpoint; // simple check first if (targetEpoch === stateEpoch) { justifiedCheckpoint = currentJustifiedCheckpoint; @@ -761,7 +759,7 @@ export function isValidAttestationData( data: phase0.AttestationData ): boolean { const {previousJustifiedCheckpoint, currentJustifiedCheckpoint} = state; - let justifiedCheckpoint; + let justifiedCheckpoint: phase0.Checkpoint; const stateEpoch = state.epochCtx.epoch; const targetEpoch = data.target.epoch; diff --git a/packages/beacon-node/src/chain/opPools/attestationPool.ts b/packages/beacon-node/src/chain/opPools/attestationPool.ts index 887448b1e553..8d8fbb92c0f1 100644 --- a/packages/beacon-node/src/chain/opPools/attestationPool.ts +++ b/packages/beacon-node/src/chain/opPools/attestationPool.ts @@ -145,11 +145,10 @@ export class AttestationPool { if (aggregate) { // Aggregate mutating return aggregateAttestationInto(aggregate, attestation); - } else { - // Create new aggregate - aggregateByIndex.set(committeeIndex, attestationToAggregate(attestation)); - return InsertOutcome.NewData; } + // Create new aggregate + aggregateByIndex.set(committeeIndex, attestationToAggregate(attestation)); + return InsertOutcome.NewData; } /** diff --git a/packages/beacon-node/src/chain/opPools/opPool.ts b/packages/beacon-node/src/chain/opPools/opPool.ts index a2180a718fee..f71186c06d35 100644 --- a/packages/beacon-node/src/chain/opPools/opPool.ts +++ b/packages/beacon-node/src/chain/opPools/opPool.ts @@ -1,4 +1,3 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; import { CachedBeaconStateAllForks, computeEpochAtSlot, @@ -16,7 +15,7 @@ import { ForkSeq, MAX_ATTESTER_SLASHINGS_ELECTRA, } from "@lodestar/params"; -import {toRootHex} from "@lodestar/utils"; +import {fromHex, toHex, toRootHex} from "@lodestar/utils"; import {Epoch, phase0, capella, ssz, ValidatorIndex, SignedBeaconBlock, AttesterSlashing} from "@lodestar/types"; import {IBeaconDb} from "../../db/index.js"; import {SignedBLSToExecutionChangeVersioned} from "../../util/types.js"; @@ -85,10 +84,10 @@ export class OpPool { persistDiff( db.attesterSlashing, Array.from(this.attesterSlashings.entries()).map(([key, value]) => ({ - key: fromHexString(key), + key: fromHex(key), value: value.attesterSlashing, })), - toHexString + toHex ), persistDiff( db.proposerSlashing, @@ -411,10 +410,9 @@ function isVoluntaryExitSignatureIncludable(stateFork: ForkSeq, voluntaryExitFor if (stateFork >= ForkSeq.deneb) { // Exists are perpetually valid https://eips.ethereum.org/EIPS/eip-7044 return true; - } else { - // Can only include exits from the current and previous fork - return voluntaryExitFork === stateFork || voluntaryExitFork === stateFork - 1; } + // Can only include exits from the current and previous fork + return voluntaryExitFork === stateFork || voluntaryExitFork === stateFork - 1; } function isSlashableAtEpoch(validator: phase0.Validator, epoch: Epoch): boolean { diff --git a/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts b/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts index bbaba1835dce..4de11e447231 100644 --- a/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts +++ b/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts @@ -88,11 +88,11 @@ export class SyncCommitteeMessagePool { if (contribution) { // Aggregate mutating return aggregateSignatureInto(contribution, signature, indexInSubcommittee); - } else { - // Create new aggregate - contributionsByRoot.set(rootHex, signatureToAggregate(subnet, signature, indexInSubcommittee)); - return InsertOutcome.NewData; } + + // Create new aggregate + contributionsByRoot.set(rootHex, signatureToAggregate(subnet, signature, indexInSubcommittee)); + return InsertOutcome.NewData; } /** diff --git a/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts b/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts index ff0feea891e1..81363be218d8 100644 --- a/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts +++ b/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts @@ -90,10 +90,9 @@ export class SyncContributionAndProofPool { const bestContribution = bestContributionBySubnet.get(subnet); if (bestContribution) { return replaceIfBetter(bestContribution, contribution, syncCommitteeParticipants); - } else { - bestContributionBySubnet.set(subnet, contributionToFast(contribution, syncCommitteeParticipants)); - return InsertOutcome.NewData; } + bestContributionBySubnet.set(subnet, contributionToFast(contribution, syncCommitteeParticipants)); + return InsertOutcome.NewData; } /** diff --git a/packages/beacon-node/src/chain/options.ts b/packages/beacon-node/src/chain/options.ts index 7c7cfcdde75b..cf83c4432984 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -1,12 +1,15 @@ import {SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; import {defaultOptions as defaultValidatorOptions} from "@lodestar/validator"; -import {ArchiverOpts} from "./archiver/index.js"; +import {ArchiverOpts} from "./archiver/interface.js"; import {ForkChoiceOpts} from "./forkChoice/index.js"; import {LightClientServerOpts} from "./lightClient/index.js"; import {ShufflingCacheOpts} from "./shufflingCache.js"; import {DEFAULT_MAX_BLOCK_STATES, FIFOBlockStateCacheOpts} from "./stateCache/fifoBlockStateCache.js"; import {PersistentCheckpointStateCacheOpts} from "./stateCache/persistentCheckpointsCache.js"; import {DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY} from "./stateCache/persistentCheckpointsCache.js"; +import {DEFAULT_STATE_ARCHIVE_MODE} from "./archiver/archiver.js"; +export {StateArchiveMode} from "./archiver/interface.js"; +export {DEFAULT_STATE_ARCHIVE_MODE} from "./archiver/archiver.js"; export type IChainOptions = BlockProcessOpts & PoolOpts & @@ -102,6 +105,7 @@ export const defaultChainOptions: IChainOptions = { suggestedFeeRecipient: defaultValidatorOptions.suggestedFeeRecipient, assertCorrectProgressiveBalances: false, archiveStateEpochFrequency: 1024, + stateArchiveMode: DEFAULT_STATE_ARCHIVE_MODE, emitPayloadAttributes: false, // for gossip block validation, it's unlikely we see a reorg with 32 slots // for attestation validation, having this value ensures we don't have to regen states most of the time @@ -111,7 +115,7 @@ export const defaultChainOptions: IChainOptions = { // batching too much may block the I/O thread so if useWorker=false, suggest this value to be 32 // since this batch attestation work is designed to work with useWorker=true, make this the lowest value minSameMessageSignatureSetsToBatch: 2, - nHistoricalStates: false, + nHistoricalStates: true, nHistoricalStatesFileDataStore: false, maxBlockStates: DEFAULT_MAX_BLOCK_STATES, maxCPStateEpochsInMemory: DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY, diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 48724ab25b0b..bda618758842 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -18,6 +18,7 @@ import {isQueueErrorAborted} from "../util/queue/index.js"; import {prepareExecutionPayload, getPayloadAttributesForSSE} from "./produceBlock/produceBlockBody.js"; import {IBeaconChain} from "./interface.js"; import {RegenCaller} from "./regen/index.js"; +import {ForkchoiceCaller} from "./forkChoice/index.js"; /* With 12s slot times, this scheduler will run 4s before the start of each slot (`12 / 3 = 4`). */ export const SCHEDULER_LOOKAHEAD_FACTOR = 3; @@ -77,7 +78,9 @@ export class PrepareNextSlotScheduler { await sleep(slotMs - slotMs / SCHEDULER_LOOKAHEAD_FACTOR, this.signal); // calling updateHead() here before we produce a block to reduce reorg possibility - const {slot: headSlot, blockRoot: headRoot} = this.chain.recomputeForkChoiceHead(); + const {slot: headSlot, blockRoot: headRoot} = this.chain.recomputeForkChoiceHead( + ForkchoiceCaller.prepareNextSlot + ); // PS: previously this was comparing slots, but that gave no leway on the skipped // slots on epoch bounday. Making it more fluid. diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index ba560d5a7ff0..a353c4e54ff9 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -17,6 +17,7 @@ import { BlindedBeaconBlockBody, BlindedBeaconBlock, sszTypesFor, + electra, } from "@lodestar/types"; import { CachedBeaconStateAllForks, @@ -219,6 +220,14 @@ export async function produceBlockBody( } else { blobsResult = {type: BlobsResultType.preDeneb}; } + + if (ForkSeq[fork] >= ForkSeq.electra) { + const {executionRequests} = builderRes; + if (executionRequests === undefined) { + throw Error(`Invalid builder getHeader response for fork=${fork}, missing executionRequests`); + } + (blockBody as electra.BlindedBeaconBlockBody).executionRequests = executionRequests; + } } // blockType === BlockType.Full @@ -258,7 +267,7 @@ export async function produceBlockBody( } const engineRes = await this.executionEngine.getPayload(fork, payloadId); - const {executionPayload, blobsBundle} = engineRes; + const {executionPayload, blobsBundle, executionRequests} = engineRes; shouldOverrideBuilder = engineRes.shouldOverrideBuilder; (blockBody as BeaconBlockBody).executionPayload = executionPayload; @@ -284,7 +293,6 @@ export async function produceBlockBody( throw Error(`Missing blobsBundle response from getPayload at fork=${fork}`); } - // validate blindedBlobsBundle if (this.opts.sanityCheckExecutionEngineBlobs) { validateBlobsAndKzgCommitments(executionPayload, blobsBundle); } @@ -298,6 +306,13 @@ export async function produceBlockBody( } else { blobsResult = {type: BlobsResultType.preDeneb}; } + + if (ForkSeq[fork] >= ForkSeq.electra) { + if (executionRequests === undefined) { + throw Error(`Missing executionRequests response from getPayload at fork=${fork}`); + } + (blockBody as electra.BeaconBlockBody).executionRequests = executionRequests; + } } } catch (e) { this.metrics?.blockPayload.payloadFetchErrors.inc(); @@ -447,6 +462,7 @@ async function prepareExecutionPayloadHeader( header: ExecutionPayloadHeader; executionPayloadValue: Wei; blobKzgCommitments?: deneb.BlobKzgCommitments; + executionRequests?: electra.ExecutionRequests; }> { if (!chain.executionBuilder) { throw Error("executionBuilder required"); @@ -473,26 +489,26 @@ export async function getExecutionPayloadParentHash( if (isMergeTransitionComplete(state)) { // Post-merge, normal payload return {isPremerge: false, parentHash: state.latestExecutionPayloadHeader.blockHash}; - } else { - if ( - !ssz.Root.equals(chain.config.TERMINAL_BLOCK_HASH, ZERO_HASH) && - getCurrentEpoch(state) < chain.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - ) - throw new Error( - `InvalidMergeTBH epoch: expected >= ${ - chain.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH - }, actual: ${getCurrentEpoch(state)}` - ); + } - const terminalPowBlockHash = await chain.eth1.getTerminalPowBlock(); - if (terminalPowBlockHash === null) { - // Pre-merge, no prepare payload call is needed - return {isPremerge: true}; - } else { - // Signify merge via producing on top of the last PoW block - return {isPremerge: false, parentHash: terminalPowBlockHash}; - } + if ( + !ssz.Root.equals(chain.config.TERMINAL_BLOCK_HASH, ZERO_HASH) && + getCurrentEpoch(state) < chain.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + ) { + throw new Error( + `InvalidMergeTBH epoch: expected >= ${ + chain.config.TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + }, actual: ${getCurrentEpoch(state)}` + ); + } + + const terminalPowBlockHash = await chain.eth1.getTerminalPowBlock(); + if (terminalPowBlockHash === null) { + // Pre-merge, no prepare payload call is needed + return {isPremerge: true}; } + // Signify merge via producing on top of the last PoW block + return {isPremerge: false, parentHash: terminalPowBlockHash}; } export async function getPayloadAttributesForSSE( @@ -528,9 +544,9 @@ export async function getPayloadAttributesForSSE( payloadAttributes, }; return ssePayloadAttributes; - } else { - throw Error("The execution is still pre-merge"); } + + throw Error("The execution is still pre-merge"); } function preparePayloadAttributes( diff --git a/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts index ba086ecafc7e..06f17c92156a 100644 --- a/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts +++ b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts @@ -5,7 +5,8 @@ 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: 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/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index 57e64bd364ea..694e8635a3b7 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -336,7 +336,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { caller: regenRequest.args[regenRequest.args.length - 1] as RegenCaller, entrypoint: regenRequest.key as RegenFnName, }; - let timer; + let timer: (() => number) | undefined; try { timer = this.metrics?.regenFnCallDuration.startTimer(metricsLabels); switch (regenRequest.key) { diff --git a/packages/beacon-node/src/chain/regen/regen.ts b/packages/beacon-node/src/chain/regen/regen.ts index 04cf5b40b494..7c663c6e0d3d 100644 --- a/packages/beacon-node/src/chain/regen/regen.ts +++ b/packages/beacon-node/src/chain/regen/regen.ts @@ -1,4 +1,3 @@ -import {fromHexString} from "@chainsafe/ssz"; import {phase0, Slot, RootHex, BeaconBlock, SignedBeaconBlock} from "@lodestar/types"; import { CachedBeaconStateAllForks, @@ -11,7 +10,7 @@ import { StateHashTreeRootSource, } from "@lodestar/state-transition"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; -import {Logger, toRootHex} from "@lodestar/utils"; +import {Logger, fromHex, toRootHex} from "@lodestar/utils"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; import {Metrics} from "../../metrics/index.js"; @@ -216,10 +215,10 @@ export class StateRegenerator implements IStateRegeneratorInternal { const protoBlocksAsc = blocksToReplay.reverse(); for (const [i, protoBlock] of protoBlocksAsc.entries()) { replaySlots[i] = protoBlock.slot; - blockPromises[i] = this.modules.db.block.get(fromHexString(protoBlock.blockRoot)); + blockPromises[i] = this.modules.db.block.get(fromHex(protoBlock.blockRoot)); } - const logCtx = {stateRoot, replaySlots: replaySlots.join(",")}; + const logCtx = {stateRoot, caller, replaySlots: replaySlots.join(",")}; this.modules.logger.debug("Replaying blocks to get state", logCtx); const loadBlocksTimer = this.modules.metrics?.regenGetState.loadBlocks.startTimer({caller}); diff --git a/packages/beacon-node/src/chain/rewards/attestationsRewards.ts b/packages/beacon-node/src/chain/rewards/attestationsRewards.ts index e909e4b1b57e..588b310f87ea 100644 --- a/packages/beacon-node/src/chain/rewards/attestationsRewards.ts +++ b/packages/beacon-node/src/chain/rewards/attestationsRewards.ts @@ -24,6 +24,7 @@ import { isInInactivityLeak, } from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; +import {fromHex} from "@lodestar/utils"; export type AttestationsRewards = routes.beacon.AttestationsRewards; type IdealAttestationsReward = routes.beacon.IdealAttestationsReward; @@ -35,9 +36,9 @@ const defaultAttestationsReward = {head: 0, target: 0, source: 0, inclusionDelay const defaultAttestationsPenalty = {target: 0, source: 0}; export async function computeAttestationsRewards( - epoch: Epoch, + _epoch: Epoch, state: CachedBeaconStateAllForks, - config: BeaconConfig, + _config: BeaconConfig, validatorIds?: (ValidatorIndex | string)[] ): Promise { const fork = state.config.getForkName(state.slot); @@ -84,7 +85,7 @@ function computeIdealAttestationsRewardsAndPenaltiesAltair( for (let i = 0; i < PARTICIPATION_FLAG_WEIGHTS.length; i++) { const weight = PARTICIPATION_FLAG_WEIGHTS[i]; - let unslashedStakeByIncrement; + let unslashedStakeByIncrement: number; let flagName: keyof IdealAttestationsReward; switch (i) { @@ -143,7 +144,7 @@ function computeTotalAttestationsRewardsAltair( const {flags} = 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(fromHex(id)))) .filter((index) => index !== undefined); // Validator indices to include in the result const inactivityPenaltyDenominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR; diff --git a/packages/beacon-node/src/chain/rewards/blockRewards.ts b/packages/beacon-node/src/chain/rewards/blockRewards.ts index 65dc23496070..19a59aaa028e 100644 --- a/packages/beacon-node/src/chain/rewards/blockRewards.ts +++ b/packages/beacon-node/src/chain/rewards/blockRewards.ts @@ -90,9 +90,9 @@ function computeSyncAggregateReward(block: altair.BeaconBlock, preState: CachedB const {syncProposerReward} = preState.epochCtx; return syncCommitteeBits.getTrueBitIndexes().length * Math.floor(syncProposerReward); // syncProposerReward should already be integer - } else { - return 0; // phase0 block does not have syncAggregate } + + return 0; // phase0 block does not have syncAggregate } /** diff --git a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts index 89ef84af43a2..71d345e32811 100644 --- a/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts +++ b/packages/beacon-node/src/chain/rewards/syncCommitteeRewards.ts @@ -51,7 +51,7 @@ export async function computeSyncCommitteeRewards( return rewards.filter( (reward) => filtersSet.has(reward.validatorIndex) || filtersSet.has(index2pubkey[reward.validatorIndex].toHex()) ); - } else { - return rewards; } + + return rewards; } diff --git a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts index 3806668436d8..1c03979b7d77 100644 --- a/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts +++ b/packages/beacon-node/src/chain/seenCache/seenGossipBlockInput.ts @@ -72,9 +72,9 @@ export class SeenGossipBlockInput { blockInput: NullBlockInput; blockInputMeta: {pending: GossipedInputType.block; haveBlobs: number; expectedBlobs: null}; } { - let blockHex; - let blockCache; - let fork; + let blockHex: RootHex; + let blockCache: BlockInputCacheType; + let fork: ForkName; if (gossipedInput.type === GossipedInputType.block) { const {signedBlock, blockBytes} = gossipedInput; @@ -149,42 +149,42 @@ export class SeenGossipBlockInput { blockInput, blockInputMeta: {pending: null, haveBlobs: allBlobs.blobs.length, expectedBlobs: blobKzgCommitments.length}, }; - } else { - const blockInput = getBlockInput.dataPromise( - config, - signedBlock, - BlockSource.gossip, - blockBytes ?? null, - cachedData - ); - - resolveBlockInput(blockInput); - return { - blockInput, - blockInputMeta: { - pending: GossipedInputType.blob, - haveBlobs: blobsCache.size, - expectedBlobs: blobKzgCommitments.length, - }, - }; - } - } else { - // will need to wait for the block to showup - if (cachedData === undefined) { - throw Error("Missing cachedData for deneb+ blobs"); } - const {blobsCache} = cachedData; + const blockInput = getBlockInput.dataPromise( + config, + signedBlock, + BlockSource.gossip, + blockBytes ?? null, + cachedData + ); + + resolveBlockInput(blockInput); return { - blockInput: { - block: null, - blockRootHex: blockHex, - cachedData, - blockInputPromise, + blockInput, + blockInputMeta: { + pending: GossipedInputType.blob, + haveBlobs: blobsCache.size, + expectedBlobs: blobKzgCommitments.length, }, - blockInputMeta: {pending: GossipedInputType.block, haveBlobs: blobsCache.size, expectedBlobs: null}, }; } + + // 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, + cachedData, + blockInputPromise, + }, + blockInputMeta: {pending: GossipedInputType.block, haveBlobs: blobsCache.size, expectedBlobs: null}, + }; } } diff --git a/packages/beacon-node/src/chain/serializeState.ts b/packages/beacon-node/src/chain/serializeState.ts index cbb2ecd18cff..c6e796cd614c 100644 --- a/packages/beacon-node/src/chain/serializeState.ts +++ b/packages/beacon-node/src/chain/serializeState.ts @@ -15,19 +15,18 @@ export async function serializeState( const size = state.type.tree_serializedSize(state.node); let stateBytes: Uint8Array | null = null; if (bufferPool) { - const bufferWithKey = bufferPool.alloc(size, source); + using bufferWithKey = bufferPool.alloc(size, source); if (bufferWithKey) { stateBytes = bufferWithKey.buffer; const dataView = new DataView(stateBytes.buffer, stateBytes.byteOffset, stateBytes.byteLength); state.serializeToBytes({uint8Array: stateBytes, dataView}, 0); + return processFn(stateBytes); } + // release the buffer back to the pool automatically } - if (!stateBytes) { - // we already have metrics in BufferPool so no need to do it here - stateBytes = state.serialize(); - } + // we already have metrics in BufferPool so no need to do it here + stateBytes = state.serialize(); return processFn(stateBytes); - // release the buffer back to the pool automatically } diff --git a/packages/beacon-node/src/chain/shufflingCache.ts b/packages/beacon-node/src/chain/shufflingCache.ts index 12dd1bf3e9ae..bdedddacf6db 100644 --- a/packages/beacon-node/src/chain/shufflingCache.ts +++ b/packages/beacon-node/src/chain/shufflingCache.ts @@ -1,9 +1,14 @@ -import {CachedBeaconStateAllForks, EpochShuffling, getShufflingDecisionBlock} from "@lodestar/state-transition"; -import {Epoch, RootHex, ssz} from "@lodestar/types"; -import {MapDef, pruneSetToMax, toRootHex} from "@lodestar/utils"; -import {GENESIS_SLOT} from "@lodestar/params"; +import { + BeaconStateAllForks, + EpochShuffling, + IShufflingCache, + ShufflingBuildProps, + computeEpochShuffling, + computeEpochShufflingAsync, +} from "@lodestar/state-transition"; +import {Epoch, RootHex} from "@lodestar/types"; +import {LodestarError, Logger, MapDef, pruneSetToMax} from "@lodestar/utils"; import {Metrics} from "../metrics/metrics.js"; -import {computeAnchorCheckpoint} from "./initState.js"; /** * Same value to CheckpointBalancesCache, with the assumption that we don't have to use it for old epochs. In the worse case: @@ -31,6 +36,7 @@ type ShufflingCacheItem = { type PromiseCacheItem = { type: CacheItemType.promise; + timeInsertedMs: number; promise: Promise; resolveFn: (shuffling: EpochShuffling) => void; }; @@ -47,7 +53,7 @@ export type ShufflingCacheOpts = { * - if a shuffling is not available (which does not happen with default chain option of maxSkipSlots = 32), track a promise to make sure we don't compute the same shuffling twice * - skip computing shuffling when loading state bytes from disk */ -export class ShufflingCache { +export class ShufflingCache implements IShufflingCache { /** LRU cache implemented as a map, pruned every time we add an item */ private readonly itemsByDecisionRootByEpoch: MapDef> = new MapDef( () => new Map() @@ -56,8 +62,10 @@ export class ShufflingCache { private readonly maxEpochs: number; constructor( - private readonly metrics: Metrics | null = null, - opts: ShufflingCacheOpts = {} + readonly metrics: Metrics | null = null, + readonly logger: Logger | null = null, + opts: ShufflingCacheOpts = {}, + precalculatedShufflings?: {shuffling: EpochShuffling | null; decisionRoot: RootHex}[] ) { if (metrics) { metrics.shufflingCache.size.addCollect(() => @@ -68,66 +76,25 @@ export class ShufflingCache { } this.maxEpochs = opts.maxShufflingCacheEpochs ?? MAX_EPOCHS; - } - - /** - * Extract shuffling from state and add to cache - */ - processState(state: CachedBeaconStateAllForks, shufflingEpoch: Epoch): EpochShuffling { - const decisionBlockHex = getDecisionBlock(state, shufflingEpoch); - let shuffling: EpochShuffling; - switch (shufflingEpoch) { - case state.epochCtx.nextShuffling.epoch: - shuffling = state.epochCtx.nextShuffling; - break; - case state.epochCtx.currentShuffling.epoch: - shuffling = state.epochCtx.currentShuffling; - break; - case state.epochCtx.previousShuffling.epoch: - shuffling = state.epochCtx.previousShuffling; - break; - default: - throw new Error(`Shuffling not found from state ${state.slot} for epoch ${shufflingEpoch}`); - } - let cacheItem = this.itemsByDecisionRootByEpoch.getOrDefault(shufflingEpoch).get(decisionBlockHex); - if (cacheItem !== undefined) { - // update existing promise - if (isPromiseCacheItem(cacheItem)) { - // unblock consumers of this promise - cacheItem.resolveFn(shuffling); - // then update item type to shuffling - cacheItem = { - type: CacheItemType.shuffling, - shuffling, - }; - this.add(shufflingEpoch, decisionBlockHex, cacheItem); - // we updated type to CacheItemType.shuffling so the above fields are not used anyway - this.metrics?.shufflingCache.processStateUpdatePromise.inc(); - } else { - // ShufflingCacheItem, do nothing - this.metrics?.shufflingCache.processStateNoOp.inc(); + precalculatedShufflings?.map(({shuffling, decisionRoot}) => { + if (shuffling !== null) { + this.set(shuffling, decisionRoot); } - } else { - // not found, new shuffling - this.add(shufflingEpoch, decisionBlockHex, {type: CacheItemType.shuffling, shuffling}); - this.metrics?.shufflingCache.processStateInsertNew.inc(); - } - - return shuffling; + }); } /** * Insert a promise to make sure we don't regen state for the same shuffling. * Bound by MAX_SHUFFLING_PROMISE to make sure our node does not blow up. */ - insertPromise(shufflingEpoch: Epoch, decisionRootHex: RootHex): void { + insertPromise(epoch: Epoch, decisionRoot: RootHex): void { const promiseCount = Array.from(this.itemsByDecisionRootByEpoch.values()) .flatMap((innerMap) => Array.from(innerMap.values())) .filter((item) => isPromiseCacheItem(item)).length; if (promiseCount >= MAX_PROMISES) { throw new Error( - `Too many shuffling promises: ${promiseCount}, shufflingEpoch: ${shufflingEpoch}, decisionRootHex: ${decisionRootHex}` + `Too many shuffling promises: ${promiseCount}, shufflingEpoch: ${epoch}, decisionRootHex: ${decisionRoot}` ); } let resolveFn: ((shuffling: EpochShuffling) => void) | null = null; @@ -140,10 +107,11 @@ export class ShufflingCache { const cacheItem: PromiseCacheItem = { type: CacheItemType.promise, + timeInsertedMs: Date.now(), promise, resolveFn, }; - this.add(shufflingEpoch, decisionRootHex, cacheItem); + this.itemsByDecisionRootByEpoch.getOrDefault(epoch).set(decisionRoot, cacheItem); this.metrics?.shufflingCache.insertPromiseCount.inc(); } @@ -152,39 +120,99 @@ export class ShufflingCache { * If there's a promise, it means we are computing the same shuffling, so we wait for the promise to resolve. * Return null if we don't have a shuffling for this epoch and dependentRootHex. */ - async get(shufflingEpoch: Epoch, decisionRootHex: RootHex): Promise { - const cacheItem = this.itemsByDecisionRootByEpoch.getOrDefault(shufflingEpoch).get(decisionRootHex); + async get(epoch: Epoch, decisionRoot: RootHex): Promise { + const cacheItem = this.itemsByDecisionRootByEpoch.getOrDefault(epoch).get(decisionRoot); if (cacheItem === undefined) { + this.metrics?.shufflingCache.miss.inc(); return null; } if (isShufflingCacheItem(cacheItem)) { + this.metrics?.shufflingCache.hit.inc(); return cacheItem.shuffling; - } else { - // promise - return cacheItem.promise; } + this.metrics?.shufflingCache.shufflingPromiseNotResolved.inc(); + return cacheItem.promise; } /** - * Same to get() function but synchronous. + * Gets a cached shuffling via the epoch and decision root. If the shuffling is not + * available it will build it synchronously and return the shuffling. + * + * NOTE: If a shuffling is already queued and not calculated it will build and resolve + * the promise but the already queued build will happen at some later time */ - getSync(shufflingEpoch: Epoch, decisionRootHex: RootHex): EpochShuffling | null { - const cacheItem = this.itemsByDecisionRootByEpoch.getOrDefault(shufflingEpoch).get(decisionRootHex); - if (cacheItem === undefined) { - return null; + getSync( + epoch: Epoch, + decisionRoot: RootHex, + buildProps?: T + ): T extends ShufflingBuildProps ? EpochShuffling : EpochShuffling | null { + const cacheItem = this.itemsByDecisionRootByEpoch.getOrDefault(epoch).get(decisionRoot); + if (!cacheItem) { + this.metrics?.shufflingCache.miss.inc(); + } else if (isShufflingCacheItem(cacheItem)) { + this.metrics?.shufflingCache.hit.inc(); + return cacheItem.shuffling; + } else if (buildProps) { + // TODO: (@matthewkeil) This should possible log a warning?? + this.metrics?.shufflingCache.shufflingPromiseNotResolvedAndThrownAway.inc(); + } else { + this.metrics?.shufflingCache.shufflingPromiseNotResolved.inc(); } - if (isShufflingCacheItem(cacheItem)) { - return cacheItem.shuffling; + let shuffling: EpochShuffling | null = null; + if (buildProps) { + const timer = this.metrics?.shufflingCache.shufflingCalculationTime.startTimer({source: "getSync"}); + shuffling = computeEpochShuffling(buildProps.state, buildProps.activeIndices, epoch); + timer?.(); + this.set(shuffling, decisionRoot); } + return shuffling as T extends ShufflingBuildProps ? EpochShuffling : EpochShuffling | null; + } - // ignore promise - return null; + /** + * Queue asynchronous build for an EpochShuffling, triggered from state-transition + */ + build(epoch: number, decisionRoot: string, state: BeaconStateAllForks, activeIndices: Uint32Array): void { + this.insertPromise(epoch, decisionRoot); + /** + * TODO: (@matthewkeil) This will get replaced by a proper build queue and a worker to do calculations + * on a NICE thread + */ + const timer = this.metrics?.shufflingCache.shufflingCalculationTime.startTimer({source: "build"}); + computeEpochShufflingAsync(state, activeIndices, epoch) + .then((shuffling) => { + this.set(shuffling, decisionRoot); + }) + .catch((err) => + this.logger?.error(`error building shuffling for epoch ${epoch} at decisionRoot ${decisionRoot}`, {}, err) + ) + .finally(() => { + timer?.(); + }); } - private add(shufflingEpoch: Epoch, decisionBlock: RootHex, cacheItem: CacheItem): void { - this.itemsByDecisionRootByEpoch.getOrDefault(shufflingEpoch).set(decisionBlock, cacheItem); + /** + * Add an EpochShuffling to the ShufflingCache. If a promise for the shuffling is present it will + * resolve the promise with the built shuffling + */ + private set(shuffling: EpochShuffling, decisionRoot: string): void { + const shufflingAtEpoch = this.itemsByDecisionRootByEpoch.getOrDefault(shuffling.epoch); + // if a pending shuffling promise exists, resolve it + const cacheItem = shufflingAtEpoch.get(decisionRoot); + if (cacheItem) { + if (isPromiseCacheItem(cacheItem)) { + cacheItem.resolveFn(shuffling); + this.metrics?.shufflingCache.shufflingPromiseResolutionTime.observe( + (Date.now() - cacheItem.timeInsertedMs) / 1000 + ); + } else { + this.metrics?.shufflingCache.shufflingBuiltMultipleTimes.inc(); + } + } + // set the shuffling + shufflingAtEpoch.set(decisionRoot, {type: CacheItemType.shuffling, shuffling}); + // prune the cache pruneSetToMax(this.itemsByDecisionRootByEpoch, this.maxEpochs); } } @@ -197,13 +225,14 @@ function isPromiseCacheItem(item: CacheItem): item is PromiseCacheItem { return item.type === CacheItemType.promise; } -/** - * Get the shuffling decision block root for the given epoch of given state - * - Special case close to genesis block, return the genesis block root - * - This is similar to forkchoice.getDependentRoot() function, otherwise we cannot get cached shuffing in attestation verification when syncing from genesis. - */ -function getDecisionBlock(state: CachedBeaconStateAllForks, epoch: Epoch): RootHex { - return state.slot > GENESIS_SLOT - ? getShufflingDecisionBlock(state, epoch) - : toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(computeAnchorCheckpoint(state.config, state).blockHeader)); +export enum ShufflingCacheErrorCode { + NO_SHUFFLING_FOUND = "SHUFFLING_CACHE_ERROR_NO_SHUFFLING_FOUND", } + +type ShufflingCacheErrorType = { + code: ShufflingCacheErrorCode.NO_SHUFFLING_FOUND; + epoch: Epoch; + decisionRoot: RootHex; +}; + +export class ShufflingCacheError extends LodestarError {} diff --git a/packages/beacon-node/src/chain/stateCache/datastore/file.ts b/packages/beacon-node/src/chain/stateCache/datastore/file.ts index 6529d12f84db..3c978c2355d6 100644 --- a/packages/beacon-node/src/chain/stateCache/datastore/file.ts +++ b/packages/beacon-node/src/chain/stateCache/datastore/file.ts @@ -1,6 +1,6 @@ import path from "node:path"; -import {toHexString, fromHexString} from "@chainsafe/ssz"; import {phase0, ssz} from "@lodestar/types"; +import {fromHex, toHex} from "@lodestar/utils"; import {ensureDir, readFile, readFileNames, removeFile, writeIfNotExist} from "../../../util/file.js"; import {CPStateDatastore, DatastoreKey} from "./types.js"; @@ -13,7 +13,7 @@ const CHECKPOINT_FILE_NAME_LENGTH = 82; export class FileCPStateDatastore implements CPStateDatastore { private readonly folderPath: string; - constructor(parentDir: string = ".") { + constructor(parentDir = ".") { // by default use the beacon folder `/beacon/checkpoint_states` this.folderPath = path.join(parentDir, CHECKPOINT_STATES_FOLDER); } @@ -28,18 +28,18 @@ export class FileCPStateDatastore implements CPStateDatastore { async write(cpKey: phase0.Checkpoint, stateBytes: Uint8Array): Promise { const serializedCheckpoint = ssz.phase0.Checkpoint.serialize(cpKey); - const filePath = path.join(this.folderPath, toHexString(serializedCheckpoint)); + const filePath = path.join(this.folderPath, toHex(serializedCheckpoint)); await writeIfNotExist(filePath, stateBytes); return serializedCheckpoint; } async remove(serializedCheckpoint: DatastoreKey): Promise { - const filePath = path.join(this.folderPath, toHexString(serializedCheckpoint)); + const filePath = path.join(this.folderPath, toHex(serializedCheckpoint)); await removeFile(filePath); } async read(serializedCheckpoint: DatastoreKey): Promise { - const filePath = path.join(this.folderPath, toHexString(serializedCheckpoint)); + const filePath = path.join(this.folderPath, toHex(serializedCheckpoint)); return readFile(filePath); } @@ -47,6 +47,6 @@ export class FileCPStateDatastore implements CPStateDatastore { const fileNames = await readFileNames(this.folderPath); return fileNames .filter((fileName) => fileName.startsWith("0x") && fileName.length === CHECKPOINT_FILE_NAME_LENGTH) - .map((fileName) => fromHexString(fileName)); + .map((fileName) => fromHex(fileName)); } } diff --git a/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts b/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts index d38fc323174c..7766daf3c5b3 100644 --- a/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts +++ b/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts @@ -13,9 +13,14 @@ export type FIFOBlockStateCacheOpts = { }; /** - * Regen state if there's a reorg distance > 32 slots. + * Given `maxSkipSlots` = 32 and `DEFAULT_EARLIEST_PERMISSIBLE_SLOT_DISTANCE` = 32, lodestar doesn't need to + * reload states in order to process a gossip block. + * + * |-----------------------------------------------|-----------------------------------------------| + * maxSkipSlots DEFAULT_EARLIEST_PERMISSIBLE_SLOT_DISTANCE ^ + * clock slot */ -export const DEFAULT_MAX_BLOCK_STATES = 32; +export const DEFAULT_MAX_BLOCK_STATES = 64; /** * New implementation of BlockStateCache that keeps the most recent n states consistently diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index 190b79e58cd6..0719efcfd309 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -1,7 +1,6 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; import {phase0, Epoch, RootHex} from "@lodestar/types"; import {CachedBeaconStateAllForks, computeStartSlotAtEpoch, getBlockRootAtSlot} from "@lodestar/state-transition"; -import {Logger, MapDef, sleep, toRootHex} from "@lodestar/utils"; +import {Logger, MapDef, fromHex, sleep, toHex, toRootHex} from "@lodestar/utils"; import {routes} from "@lodestar/api"; import {loadCachedBeaconState} from "@lodestar/state-transition"; import {INTERVALS_PER_SLOT} from "@lodestar/params"; @@ -54,10 +53,11 @@ type CacheItem = InMemoryCacheItem | PersistedCacheItem; type LoadedStateBytesData = {persistedKey: DatastoreKey; stateBytes: Uint8Array}; /** - * Before n-historical states, lodestar keeps mostly 3 states in memory with 1 finalized state - * Since Jan 2024, lodestar stores the finalized state in disk and keeps up to 2 epochs in memory + * Before n-historical states, lodestar keeps all checkpoint states since finalized + * Since Sep 2024, lodestar stores 3 most recent checkpoint states in memory and the rest on disk. The finalized state + * may not be available in memory, and stay on disk instead. */ -export const DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY = 2; +export const DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY = 3; /** * An implementation of CheckpointStateCache that keep up to n epoch checkpoint states in memory and persist the rest to disk @@ -95,7 +95,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { private readonly cache: MapTracker; /** Epoch -> Set */ private readonly epochIndex = new MapDef>(() => new Set()); - private readonly metrics: Metrics["cpStateCache"] | null | undefined; + private readonly metrics: Metrics | null | undefined; private readonly logger: Logger; private readonly clock: IClock | null | undefined; private readonly signal: AbortSignal | undefined; @@ -124,7 +124,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { ) { this.cache = new MapTracker(metrics?.cpStateCache); if (metrics) { - this.metrics = metrics.cpStateCache; + this.metrics = metrics; metrics.cpStateCache.size.addCollect(() => { let persistCount = 0; let inMemoryCount = 0; @@ -193,40 +193,29 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { return stateOrStateBytesData?.clone(opts?.dontTransferCache) ?? null; } const {persistedKey, stateBytes} = stateOrStateBytesData; - const logMeta = {persistedKey: toHexString(persistedKey)}; + const logMeta = {persistedKey: toHex(persistedKey)}; this.logger.debug("Reload: read state successful", logMeta); - this.metrics?.stateReloadSecFromSlot.observe(this.clock?.secFromSlot(this.clock?.currentSlot ?? 0) ?? 0); + this.metrics?.cpStateCache.stateReloadSecFromSlot.observe( + this.clock?.secFromSlot(this.clock?.currentSlot ?? 0) ?? 0 + ); const seedState = this.findSeedStateToReload(cp); - this.metrics?.stateReloadEpochDiff.observe(Math.abs(seedState.epochCtx.epoch - cp.epoch)); + this.metrics?.cpStateCache.stateReloadEpochDiff.observe(Math.abs(seedState.epochCtx.epoch - cp.epoch)); this.logger.debug("Reload: found seed state", {...logMeta, seedSlot: seedState.slot}); try { // 80% of validators serialization time comes from memory allocation, this is to avoid it - const sszTimer = this.metrics?.stateReloadValidatorsSerializeDuration.startTimer(); + const sszTimer = this.metrics?.cpStateCache.stateReloadValidatorsSerializeDuration.startTimer(); // automatically free the buffer pool after this scope using validatorsBytesWithKey = this.serializeStateValidators(seedState); let validatorsBytes = validatorsBytesWithKey?.buffer; if (validatorsBytes == null) { // fallback logic in case we can't use the buffer pool - this.metrics?.stateReloadValidatorsSerializeAllocCount.inc(); + this.metrics?.cpStateCache.stateReloadValidatorsSerializeAllocCount.inc(); validatorsBytes = seedState.validators.serialize(); } sszTimer?.(); - const timer = this.metrics?.stateReloadDuration.startTimer(); - const newCachedState = loadCachedBeaconState( - seedState, - stateBytes, - { - shufflingGetter: (shufflingEpoch, decisionRootHex) => { - const shuffling = this.shufflingCache.getSync(shufflingEpoch, decisionRootHex); - if (shuffling == null) { - this.metrics?.stateReloadShufflingCacheMiss.inc(); - } - return shuffling; - }, - }, - validatorsBytes - ); + const timer = this.metrics?.cpStateCache.stateReloadDuration.startTimer(); + const newCachedState = loadCachedBeaconState(seedState, stateBytes, {}, validatorsBytes); newCachedState.commit(); const stateRoot = toRootHex(newCachedState.hashTreeRoot()); timer?.(); @@ -289,7 +278,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { } const persistedKey = cacheItem.value; - const dbReadTimer = this.metrics?.stateReloadDbReadTime.startTimer(); + const dbReadTimer = this.metrics?.cpStateCache.stateReloadDbReadTime.startTimer(); const stateBytes = await this.datastore.read(persistedKey); dbReadTimer?.(); @@ -303,7 +292,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { * Similar to get() api without reloading from disk */ get(cpOrKey: CheckpointHex | string, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { - this.metrics?.lookups.inc(); + this.metrics?.cpStateCache.lookups.inc(); const cpKey = typeof cpOrKey === "string" ? cpOrKey : toCacheKey(cpOrKey); const cacheItem = this.cache.get(cpKey); @@ -311,7 +300,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { return null; } - this.metrics?.hits.inc(); + this.metrics?.cpStateCache.hits.inc(); if (cpKey === this.preComputedCheckpoint) { this.preComputedCheckpointHits = (this.preComputedCheckpointHits ?? 0) + 1; @@ -319,7 +308,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { if (isInMemoryCacheItem(cacheItem)) { const {state} = cacheItem; - this.metrics?.stateClonedCount.observe(state.clonedCount); + this.metrics?.cpStateCache.stateClonedCount.observe(state.clonedCount); return state.clone(opts?.dontTransferCache); } @@ -333,7 +322,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { const cpHex = toCheckpointHex(cp); const key = toCacheKey(cpHex); const cacheItem = this.cache.get(key); - this.metrics?.adds.inc(); + this.metrics?.cpStateCache.adds.inc(); if (cacheItem !== undefined && isPersistedCacheItem(cacheItem)) { const persistedKey = cacheItem.value; // was persisted to disk, set back to memory @@ -341,7 +330,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { this.logger.verbose("Added checkpoint state to memory but a persisted key existed", { epoch: cp.epoch, rootHex: cpHex.rootHex, - persistedKey: toHexString(persistedKey), + persistedKey: toHex(persistedKey), }); } else { this.cache.set(key, {type: CacheItemType.inMemory, state}); @@ -657,7 +646,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { let persistCount = 0; const epochBoundarySlot = computeStartSlotAtEpoch(epoch); const epochBoundaryRoot = - epochBoundarySlot === state.slot ? fromHexString(blockRootHex) : getBlockRootAtSlot(state, epochBoundarySlot); + epochBoundarySlot === state.slot ? fromHex(blockRootHex) : getBlockRootAtSlot(state, epochBoundarySlot); const epochBoundaryHex = toRootHex(epochBoundaryRoot); const prevEpochRoot = toRootHex(getBlockRootAtSlot(state, epochBoundarySlot - 1)); @@ -688,7 +677,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { stateSlot: state.slot, rootHex, epochBoundaryHex, - persistedKey: persistedKey ? toHexString(persistedKey) : "", + persistedKey: persistedKey ? toHex(persistedKey) : "", }; if (persistedRootHexes.has(rootHex)) { @@ -697,26 +686,33 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { this.logger.verbose("Pruned checkpoint state from memory but no need to persist", logMeta); } else { // persist and do not update epochIndex - this.metrics?.statePersistSecFromSlot.observe(this.clock?.secFromSlot(this.clock?.currentSlot ?? 0) ?? 0); - const cpPersist = {epoch: epoch, root: fromHexString(rootHex)}; + this.metrics?.cpStateCache.statePersistSecFromSlot.observe( + this.clock?.secFromSlot(this.clock?.currentSlot ?? 0) ?? 0 + ); + const cpPersist = {epoch: epoch, root: fromHex(rootHex)}; // It's not sustainable to allocate ~240MB for each state every epoch, so we use buffer pool to reuse the memory. // As monitored on holesky as of Jan 2024: // - This does not increase heap allocation while gc time is the same // - It helps stabilize persist time and save ~300ms in average (1.5s vs 1.2s) // - It also helps the state reload to save ~500ms in average (4.3s vs 3.8s) // - Also `serializeState.test.ts` perf test shows a lot of differences allocating ~240MB once vs per state serialization - const timer = this.metrics?.stateSerializeDuration.startTimer(); + const timer = this.metrics?.stateSerializeDuration.startTimer({ + source: AllocSource.PERSISTENT_CHECKPOINTS_CACHE_STATE, + }); persistedKey = await serializeState( state, AllocSource.PERSISTENT_CHECKPOINTS_CACHE_STATE, - (stateBytes) => this.datastore.write(cpPersist, stateBytes), + (stateBytes) => { + timer?.(); + return this.datastore.write(cpPersist, stateBytes); + }, this.bufferPool ); - timer?.(); + persistCount++; this.logger.verbose("Pruned checkpoint state from memory and persisted to disk", { ...logMeta, - persistedKey: toHexString(persistedKey), + persistedKey: toHex(persistedKey), }); } // overwrite cpKey, this means the state is deleted from memory @@ -732,7 +728,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { this.cache.delete(cpKey); this.epochIndex.get(epoch)?.delete(rootHex); } - this.metrics?.statePruneFromMemoryCount.inc(); + this.metrics?.cpStateCache.statePruneFromMemoryCount.inc(); this.logger.verbose("Pruned checkpoint state from memory", logMeta); } } @@ -756,7 +752,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { if (persistedKey) { await this.datastore.remove(persistedKey); persistCount++; - this.metrics?.persistedStateRemoveCount.inc(); + this.metrics?.cpStateCache.persistedStateRemoveCount.inc(); } } this.cache.delete(key); diff --git a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts index 39a3700aacf9..6d6eab6ec15a 100644 --- a/packages/beacon-node/src/chain/validation/aggregateAndProof.ts +++ b/packages/beacon-node/src/chain/validation/aggregateAndProof.ts @@ -6,7 +6,7 @@ import { createAggregateSignatureSetFromComponents, } from "@lodestar/state-transition"; import {toRootHex} from "@lodestar/utils"; -import {IBeaconChain} from ".."; +import {IBeaconChain} from "../index.js"; import {AttestationError, AttestationErrorCode, GossipAction} from "../errors/index.js"; import {RegenCaller} from "../regen/index.js"; import {getSelectionProofSignatureSet, getAggregateAndProofSignatureSet} from "./signatureSets/index.js"; @@ -74,7 +74,7 @@ async function validateAggregateAndProof( const seenAttDataKey = serializedData ? getSeenAttDataKeyFromSignedAggregateAndProof(fork, serializedData) : null; const cachedAttData = seenAttDataKey ? chain.seenAttestationDatas.get(attSlot, seenAttDataKey) : null; - let attIndex; + let attIndex: number | null; if (ForkSeq[fork] >= ForkSeq.electra) { attIndex = (aggregate as electra.Attestation).committeeBits.getSingleTrueBit(); // [REJECT] len(committee_indices) == 1, where committee_indices = get_committee_indices(aggregate) diff --git a/packages/beacon-node/src/chain/validation/attestation.ts b/packages/beacon-node/src/chain/validation/attestation.ts index 4ceaf64d658d..0b3f490e4129 100644 --- a/packages/beacon-node/src/chain/validation/attestation.ts +++ b/packages/beacon-node/src/chain/validation/attestation.ts @@ -38,7 +38,6 @@ import {MAXIMUM_GOSSIP_CLOCK_DISPARITY_SEC} from "../../constants/index.js"; import {RegenCaller} from "../regen/index.js"; import { getAggregationBitsFromAttestationSerialized, - getAttDataFromAttestationSerialized, getAttDataFromSignedAggregateAndProofElectra, getCommitteeBitsFromAttestationSerialized, getCommitteeBitsFromSignedAggregateAndProofElectra, @@ -75,9 +74,8 @@ export type GossipAttestation = { serializedData: Uint8Array; // available in NetworkProcessor since we check for unknown block root attestations attSlot: Slot; - // for old LIFO linear gossip queue we don't have attDataBase64 // for indexed gossip queue we have attDataBase64 - attDataBase64?: SeenAttDataKey | null; + attDataBase64: SeenAttDataKey; }; export type Step0Result = AttestationValidationResult & { @@ -85,20 +83,6 @@ export type Step0Result = AttestationValidationResult & { validatorIndex: number; }; -/** - * Validate a single gossip attestation, do not prioritize bls signature set - */ -export async function validateGossipAttestation( - fork: ForkName, - chain: IBeaconChain, - attestationOrBytes: GossipAttestation, - /** Optional, to allow verifying attestations through API with unknown subnet */ - subnet: number -): Promise { - const prioritizeBls = false; - return validateAttestation(fork, chain, attestationOrBytes, subnet, prioritizeBls); -} - /** * Verify gossip attestations of the same attestation data. The main advantage is we can batch verify bls signatures * through verifySignatureSetsSameMessage bls api to improve performance. @@ -111,7 +95,7 @@ export async function validateGossipAttestationsSameAttData( attestationOrBytesArr: GossipAttestation[], subnet: number, // for unit test, consumers do not need to pass this - step0ValidationFn = validateGossipAttestationNoSignatureCheck + step0ValidationFn = validateAttestationNoSignatureCheck ): Promise { if (attestationOrBytesArr.length === 0) { return {results: [], batchableBls: false}; @@ -213,22 +197,10 @@ export async function validateApiAttestation( attestationOrBytes: ApiAttestation ): Promise { const prioritizeBls = true; - return validateAttestation(fork, chain, attestationOrBytes, null, prioritizeBls); -} + const subnet = null; -/** - * Validate a single unaggregated attestation - * subnet is null for api attestations - */ -export async function validateAttestation( - fork: ForkName, - chain: IBeaconChain, - attestationOrBytes: AttestationOrBytes, - subnet: number | null, - prioritizeBls = false -): Promise { try { - const step0Result = await validateGossipAttestationNoSignatureCheck(fork, chain, attestationOrBytes, subnet); + const step0Result = await validateAttestationNoSignatureCheck(fork, chain, attestationOrBytes, subnet); const {attestation, signatureSet, validatorIndex} = step0Result; const isValid = await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}); @@ -236,19 +208,18 @@ export async function validateAttestation( const targetEpoch = attestation.data.target.epoch; chain.seenAttesters.add(targetEpoch, validatorIndex); return step0Result; - } else { - throw new AttestationError(GossipAction.IGNORE, { - code: AttestationErrorCode.INVALID_SIGNATURE, - }); } + + throw new AttestationError(GossipAction.IGNORE, { + code: AttestationErrorCode.INVALID_SIGNATURE, + }); } catch (err) { if (err instanceof EpochCacheError && err.type.code === EpochCacheErrorCode.COMMITTEE_INDEX_OUT_OF_RANGE) { throw new AttestationError(GossipAction.IGNORE, { code: AttestationErrorCode.BAD_TARGET_EPOCH, }); - } else { - throw err; } + throw err; } } @@ -256,7 +227,7 @@ export async function validateAttestation( * Only deserialize the attestation if needed, use the cached AttestationData instead * This is to avoid deserializing similar attestation multiple times which could help the gc */ -async function validateGossipAttestationNoSignatureCheck( +async function validateAttestationNoSignatureCheck( fork: ForkName, chain: IBeaconChain, attestationOrBytes: AttestationOrBytes, @@ -302,7 +273,7 @@ async function validateGossipAttestationNoSignatureCheck( const attEpoch = computeEpochAtSlot(attSlot); const attTarget = attData.target; const targetEpoch = attTarget.epoch; - let committeeIndex; + let committeeIndex: number | null; if (attestationOrCache.attestation) { if (isElectraAttestation(attestationOrCache.attestation)) { // api or first time validation of a gossip attestation @@ -743,27 +714,27 @@ function verifyAttestationTargetRoot(headBlock: ProtoBlock, targetRoot: Root, at targetRoot: toRootHex(targetRoot), expected: null, }); - } else { - const expectedTargetRoot = - headBlockEpoch === attestationEpoch - ? // If the block is in the same epoch as the attestation, then use the target root - // from the block. - headBlock.targetRoot - : // If the head block is from a previous epoch then skip slots will cause the head block - // root to become the target block root. - // - // We know the head block is from a previous epoch due to a previous check. - headBlock.blockRoot; - - // TODO: Do a fast comparision to convert and compare byte by byte - if (expectedTargetRoot !== toRootHex(targetRoot)) { - // Reject any attestation with an invalid target root. - throw new AttestationError(GossipAction.REJECT, { - code: AttestationErrorCode.INVALID_TARGET_ROOT, - targetRoot: toRootHex(targetRoot), - expected: expectedTargetRoot, - }); - } + } + + const expectedTargetRoot = + headBlockEpoch === attestationEpoch + ? // If the block is in the same epoch as the attestation, then use the target root + // from the block. + headBlock.targetRoot + : // If the head block is from a previous epoch then skip slots will cause the head block + // root to become the target block root. + // + // We know the head block is from a previous epoch due to a previous check. + headBlock.blockRoot; + + // TODO: Do a fast comparision to convert and compare byte by byte + if (expectedTargetRoot !== toRootHex(targetRoot)) { + // Reject any attestation with an invalid target root. + throw new AttestationError(GossipAction.REJECT, { + code: AttestationErrorCode.INVALID_TARGET_ROOT, + targetRoot: toRootHex(targetRoot), + expected: expectedTargetRoot, + }); } } @@ -801,9 +772,6 @@ export function computeSubnetForSlot(shuffling: EpochShuffling, slot: number, co * Return fork-dependent seen attestation key * - for pre-electra, it's the AttestationData base64 * - for electra and later, it's the AttestationData base64 + committeeBits base64 - * - * we always have attDataBase64 from the IndexedGossipQueue, getAttDataFromAttestationSerialized() just for backward compatible when beaconAttestationBatchValidation is false - * TODO: remove beaconAttestationBatchValidation flag since the batch attestation is stable */ export function getSeenAttDataKeyFromGossipAttestation( fork: ForkName, @@ -811,13 +779,12 @@ export function getSeenAttDataKeyFromGossipAttestation( ): SeenAttDataKey | null { const {attDataBase64, serializedData} = attestation; if (isForkPostElectra(fork)) { - const attData = attDataBase64 ?? getAttDataFromAttestationSerialized(serializedData); const committeeBits = getCommitteeBitsFromAttestationSerialized(serializedData); - return attData && committeeBits ? attDataBase64 + committeeBits : null; + return attDataBase64 && committeeBits ? attDataBase64 + committeeBits : null; } // pre-electra - return attDataBase64 ?? getAttDataFromAttestationSerialized(serializedData); + return attDataBase64; } /** diff --git a/packages/beacon-node/src/chain/validation/attesterSlashing.ts b/packages/beacon-node/src/chain/validation/attesterSlashing.ts index 11a499c9bb53..5da146a67a86 100644 --- a/packages/beacon-node/src/chain/validation/attesterSlashing.ts +++ b/packages/beacon-node/src/chain/validation/attesterSlashing.ts @@ -4,7 +4,7 @@ import { assertValidAttesterSlashing, getAttesterSlashingSignatureSets, } from "@lodestar/state-transition"; -import {IBeaconChain} from ".."; +import {IBeaconChain} from "../index.js"; import {AttesterSlashingError, AttesterSlashingErrorCode, GossipAction} from "../errors/index.js"; export async function validateApiAttesterSlashing( diff --git a/packages/beacon-node/src/chain/validation/blsToExecutionChange.ts b/packages/beacon-node/src/chain/validation/blsToExecutionChange.ts index e5ad56daeb5c..fff399aada50 100644 --- a/packages/beacon-node/src/chain/validation/blsToExecutionChange.ts +++ b/packages/beacon-node/src/chain/validation/blsToExecutionChange.ts @@ -4,7 +4,7 @@ import { getBlsToExecutionChangeSignatureSet, CachedBeaconStateCapella, } from "@lodestar/state-transition"; -import {IBeaconChain} from ".."; +import {IBeaconChain} from "../index.js"; import {BlsToExecutionChangeError, BlsToExecutionChangeErrorCode, GossipAction} from "../errors/index.js"; export async function validateApiBlsToExecutionChange( diff --git a/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts b/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts index f4865c73f8e4..0b19495abde6 100644 --- a/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts +++ b/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts @@ -34,9 +34,9 @@ export function validateLightClientFinalityUpdate( } // [IGNORE] The received finality_update matches the locally computed one exactly - const sszType = config.getLightClientForkTypes(gossipedFinalityUpdate.attestedHeader.beacon.slot)[ - "LightClientFinalityUpdate" - ]; + const sszType = config.getLightClientForkTypes( + gossipedFinalityUpdate.attestedHeader.beacon.slot + ).LightClientFinalityUpdate; if (localFinalityUpdate === null || !sszType.equals(gossipedFinalityUpdate, localFinalityUpdate)) { throw new LightClientError(GossipAction.IGNORE, { code: LightClientErrorCode.FINALITY_UPDATE_NOT_MATCHING_LOCAL, diff --git a/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts b/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts index 182321984af5..7a7dd7f34a91 100644 --- a/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts +++ b/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts @@ -35,9 +35,9 @@ export function validateLightClientOptimisticUpdate( } // [IGNORE] The received optimistic_update matches the locally computed one exactly - const sszType = config.getLightClientForkTypes(gossipedOptimisticUpdate.attestedHeader.beacon.slot)[ - "LightClientOptimisticUpdate" - ]; + const sszType = config.getLightClientForkTypes( + gossipedOptimisticUpdate.attestedHeader.beacon.slot + ).LightClientOptimisticUpdate; if (localOptimisticUpdate === null || !sszType.equals(gossipedOptimisticUpdate, localOptimisticUpdate)) { throw new LightClientError(GossipAction.IGNORE, { code: LightClientErrorCode.OPTIMISTIC_UPDATE_NOT_MATCHING_LOCAL, diff --git a/packages/beacon-node/src/chain/validation/proposerSlashing.ts b/packages/beacon-node/src/chain/validation/proposerSlashing.ts index 7281302aae8d..48fe6db326c9 100644 --- a/packages/beacon-node/src/chain/validation/proposerSlashing.ts +++ b/packages/beacon-node/src/chain/validation/proposerSlashing.ts @@ -1,6 +1,6 @@ import {phase0} from "@lodestar/types"; import {assertValidProposerSlashing, getProposerSlashingSignatureSets} from "@lodestar/state-transition"; -import {IBeaconChain} from ".."; +import {IBeaconChain} from "../index.js"; import {ProposerSlashingError, ProposerSlashingErrorCode, GossipAction} from "../errors/index.js"; export async function validateApiProposerSlashing( diff --git a/packages/beacon-node/src/chain/validation/voluntaryExit.ts b/packages/beacon-node/src/chain/validation/voluntaryExit.ts index 3957b51180d9..2b38b161423e 100644 --- a/packages/beacon-node/src/chain/validation/voluntaryExit.ts +++ b/packages/beacon-node/src/chain/validation/voluntaryExit.ts @@ -1,6 +1,6 @@ import {phase0} from "@lodestar/types"; import {isValidVoluntaryExit, getVoluntaryExitSignatureSet} from "@lodestar/state-transition"; -import {IBeaconChain} from ".."; +import {IBeaconChain} from "../index.js"; import {VoluntaryExitError, VoluntaryExitErrorCode, GossipAction} from "../errors/index.js"; import {RegenCaller} from "../regen/index.js"; diff --git a/packages/beacon-node/src/db/buckets.ts b/packages/beacon-node/src/db/buckets.ts index eff123879037..c35a5a18edf5 100644 --- a/packages/beacon-node/src/db/buckets.ts +++ b/packages/beacon-node/src/db/buckets.ts @@ -65,11 +65,10 @@ export enum Bucket { export function getBucketNameByValue(enumValue: T): keyof typeof Bucket { const keys = Object.keys(Bucket).filter((x) => { - if (isNaN(parseInt(x))) { - return Bucket[x as keyof typeof Bucket] == enumValue; - } else { - return false; + if (Number.isNaN(parseInt(x))) { + return Bucket[x as keyof typeof Bucket] === enumValue; } + return false; }) as (keyof typeof Bucket)[]; if (keys.length > 0) { return keys[0]; diff --git a/packages/beacon-node/src/db/repositories/backfilledRanges.ts b/packages/beacon-node/src/db/repositories/backfilledRanges.ts index 309909b18cb8..f86c20288099 100644 --- a/packages/beacon-node/src/db/repositories/backfilledRanges.ts +++ b/packages/beacon-node/src/db/repositories/backfilledRanges.ts @@ -23,8 +23,7 @@ export class BackfilledRanges extends Repository { return bytesToInt(super.decodeKey(data) as unknown as Uint8Array, "be"); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getId(value: Slot): number { + getId(_value: Slot): number { throw new Error("Cannot get the db key from slot"); } } diff --git a/packages/beacon-node/src/db/repositories/blockArchive.ts b/packages/beacon-node/src/db/repositories/blockArchive.ts index 15c07f552b21..427650a37bc3 100644 --- a/packages/beacon-node/src/db/repositories/blockArchive.ts +++ b/packages/beacon-node/src/db/repositories/blockArchive.ts @@ -105,7 +105,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; + const step = opts?.step ?? 1; for await (const value of valuesStream) { if ((value.message.slot - firstSlot) % step === 0) { diff --git a/packages/beacon-node/src/db/repositories/blockArchiveIndex.ts b/packages/beacon-node/src/db/repositories/blockArchiveIndex.ts index 797142d09db7..8c2785dbe67c 100644 --- a/packages/beacon-node/src/db/repositories/blockArchiveIndex.ts +++ b/packages/beacon-node/src/db/repositories/blockArchiveIndex.ts @@ -17,7 +17,7 @@ export async function deleteRootIndex( signedBeaconBlockType: SSZTypesFor, block: SignedBeaconBlock ): Promise { - const beaconBlockType = (signedBeaconBlockType as typeof ssz.phase0.SignedBeaconBlock).fields["message"]; + const beaconBlockType = (signedBeaconBlockType as typeof ssz.phase0.SignedBeaconBlock).fields.message; return db.delete(getRootIndexKey(beaconBlockType.hashTreeRoot(block.message))); } diff --git a/packages/beacon-node/src/db/repositories/checkpointState.ts b/packages/beacon-node/src/db/repositories/checkpointState.ts index 8848f4d26d3a..7bace3f3f3fc 100644 --- a/packages/beacon-node/src/db/repositories/checkpointState.ts +++ b/packages/beacon-node/src/db/repositories/checkpointState.ts @@ -11,10 +11,10 @@ import {Bucket, getBucketNameByValue} from "../buckets.js"; export class CheckpointStateRepository extends Repository { constructor(config: ChainForkConfig, db: Db) { // Pick some type but won't be used. Casted to any because no type can match `BeaconStateAllForks` - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any - const type = ssz.phase0.BeaconState as any; + const type = ssz.phase0.BeaconState; const bucket = Bucket.allForks_checkpointState; - super(config, db, bucket, type, getBucketNameByValue(bucket)); + // biome-ignore lint/suspicious/noExplicitAny: The type is complex to specify a proper override + super(config, db, bucket, type as any, getBucketNameByValue(bucket)); } getId(): Uint8Array { diff --git a/packages/beacon-node/src/db/repositories/depositDataRoot.ts b/packages/beacon-node/src/db/repositories/depositDataRoot.ts index fa8983f0e5fa..97200ccd0f92 100644 --- a/packages/beacon-node/src/db/repositories/depositDataRoot.ts +++ b/packages/beacon-node/src/db/repositories/depositDataRoot.ts @@ -21,8 +21,7 @@ export class DepositDataRootRepository extends Repository { } // depositDataRoots stored by depositData index - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getId(value: Root): number { + getId(_value: Root): number { throw new Error("Unable to create depositIndex from root"); } @@ -70,7 +69,9 @@ export class DepositDataRootRepository extends Repository { // TODO: Review and fix properly if (index > depositRootTree.length) { throw Error(`Error setting depositRootTree index ${index} > length ${depositRootTree.length}`); - } else if (index === depositRootTree.length) { + } + + if (index === depositRootTree.length) { depositRootTree.push(value); } else { depositRootTree.set(index, value); diff --git a/packages/beacon-node/src/db/repositories/eth1Data.ts b/packages/beacon-node/src/db/repositories/eth1Data.ts index e0a6c14e4022..f87199a0b80e 100644 --- a/packages/beacon-node/src/db/repositories/eth1Data.ts +++ b/packages/beacon-node/src/db/repositories/eth1Data.ts @@ -14,8 +14,7 @@ export class Eth1DataRepository extends Repository { constructor(config: ChainForkConfig, db: Db) { // Pick some type but won't be used. Casted to any because no type can match `BeaconStateAllForks` - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: const type = ssz.phase0.BeaconState as any; const bucket = Bucket.allForks_stateArchive; super(config, db, bucket, type, getBucketNameByValue(bucket)); diff --git a/packages/beacon-node/src/db/single/preGenesisStateLastProcessedBlock.ts b/packages/beacon-node/src/db/single/preGenesisStateLastProcessedBlock.ts index c958b9de2f0a..37dcb069acc9 100644 --- a/packages/beacon-node/src/db/single/preGenesisStateLastProcessedBlock.ts +++ b/packages/beacon-node/src/db/single/preGenesisStateLastProcessedBlock.ts @@ -10,7 +10,7 @@ export class PreGenesisStateLastProcessedBlock { private readonly db: Db; private readonly key: Uint8Array; - constructor(config: ChainForkConfig, db: Db) { + constructor(_config: ChainForkConfig, db: Db) { this.db = db; this.type = ssz.UintNum64; this.bucket = Bucket.phase0_preGenesisStateLastProcessedBlock; diff --git a/packages/beacon-node/src/eth1/eth1DataCache.ts b/packages/beacon-node/src/eth1/eth1DataCache.ts index 91b8537d1cc4..7f9bb99a2f16 100644 --- a/packages/beacon-node/src/eth1/eth1DataCache.ts +++ b/packages/beacon-node/src/eth1/eth1DataCache.ts @@ -21,6 +21,6 @@ export class Eth1DataCache { async getHighestCachedBlockNumber(): Promise { const highestEth1Data = await this.db.eth1Data.lastValue(); - return highestEth1Data && highestEth1Data.blockNumber; + return highestEth1Data?.blockNumber ?? null; } } diff --git a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts index a38b3f9987d9..674b6b600f31 100644 --- a/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts +++ b/packages/beacon-node/src/eth1/eth1DepositDataTracker.ts @@ -14,7 +14,7 @@ import {Eth1DepositsCache} from "./eth1DepositsCache.js"; import {Eth1DataCache} from "./eth1DataCache.js"; import {getEth1VotesToConsider, pickEth1Vote} from "./utils/eth1Vote.js"; import {getDeposits} from "./utils/deposits.js"; -import {Eth1DataAndDeposits, IEth1Provider} from "./interface.js"; +import {Eth1DataAndDeposits, EthJsonRpcBlockRaw, IEth1Provider} from "./interface.js"; import {Eth1Options} from "./options.js"; import {HttpRpcError} from "./provider/jsonRpcHttpClient.js"; import {parseEth1Block} from "./provider/eth1Provider.js"; @@ -243,7 +243,7 @@ export class Eth1DepositDataTracker { const fromBlock = Math.min(remoteFollowBlock, this.getFromBlockToFetch(lastProcessedDepositBlockNumber)); const toBlock = Math.min(remoteFollowBlock, fromBlock + this.eth1GetLogsBatchSizeDynamic - 1); - let depositEvents; + let depositEvents: phase0.DepositEvent[]; try { depositEvents = await this.eth1Provider.getDepositEvents(fromBlock, toBlock); // Increase the batch size linearly even if we scale down exponentially (half each time) @@ -313,7 +313,7 @@ export class Eth1DepositDataTracker { lastProcessedDepositBlockNumber ); - let blocksRaw; + let blocksRaw: EthJsonRpcBlockRaw[]; try { blocksRaw = await this.eth1Provider.getBlocksByNumber(fromBlock, toBlock); // Increase the batch size linearly even if we scale down exponentially (half each time) @@ -380,26 +380,24 @@ export class Eth1DepositDataTracker { this.eth1FollowDistance = Math.min(this.eth1FollowDistance + delta, this.config.ETH1_FOLLOW_DISTANCE); return true; - } else { - // Blocks are slower than expected, reduce eth1FollowDistance. Limit min CATCHUP_MIN_FOLLOW_DISTANCE - const delta = - this.eth1FollowDistance - - Math.max(this.eth1FollowDistance - ETH1_FOLLOW_DISTANCE_DELTA_IF_SLOW, ETH_MIN_FOLLOW_DISTANCE); - this.eth1FollowDistance = this.eth1FollowDistance - delta; - - // Even if the blocks are slow, when we are all caught up as there is no - // further possibility to reduce follow distance, we need to call it quits - // for now, else it leads to an incessant poll on the EL - return delta === 0; } + // Blocks are slower than expected, reduce eth1FollowDistance. Limit min CATCHUP_MIN_FOLLOW_DISTANCE + const delta = + this.eth1FollowDistance - + Math.max(this.eth1FollowDistance - ETH1_FOLLOW_DISTANCE_DELTA_IF_SLOW, ETH_MIN_FOLLOW_DISTANCE); + this.eth1FollowDistance = this.eth1FollowDistance - delta; + + // Even if the blocks are slow, when we are all caught up as there is no + // further possibility to reduce follow distance, we need to call it quits + // for now, else it leads to an incessant poll on the EL + return delta === 0; } private getFromBlockToFetch(lastCachedBlock: number | null): number { if (lastCachedBlock === null) { return this.eth1Provider.deployBlock ?? 0; - } else { - return lastCachedBlock + 1; } + return lastCachedBlock + 1; } private async getLastProcessedDepositBlockNumber(): Promise { diff --git a/packages/beacon-node/src/eth1/eth1DepositsCache.ts b/packages/beacon-node/src/eth1/eth1DepositsCache.ts index 2fb187d02cf7..13dd29013124 100644 --- a/packages/beacon-node/src/eth1/eth1DepositsCache.ts +++ b/packages/beacon-node/src/eth1/eth1DepositsCache.ts @@ -129,7 +129,7 @@ export class Eth1DepositsCache { */ async getHighestDepositEventBlockNumber(): Promise { const latestEvent = await this.db.depositEvent.lastValue(); - return latestEvent && latestEvent.blockNumber; + return latestEvent?.blockNumber || null; } /** @@ -137,6 +137,6 @@ export class Eth1DepositsCache { */ async getLowestDepositEventBlockNumber(): Promise { const firstEvent = await this.db.depositEvent.firstValue(); - return firstEvent && firstEvent.blockNumber; + return firstEvent?.blockNumber || null; } } diff --git a/packages/beacon-node/src/eth1/eth1MergeBlockTracker.ts b/packages/beacon-node/src/eth1/eth1MergeBlockTracker.ts index 9ba4a09a5549..5bf76625dfe8 100644 --- a/packages/beacon-node/src/eth1/eth1MergeBlockTracker.ts +++ b/packages/beacon-node/src/eth1/eth1MergeBlockTracker.ts @@ -126,11 +126,10 @@ export class Eth1MergeBlockTracker { td: this.latestEth1Block.totalDifficulty, timestamp: this.latestEth1Block.timestamp, }; - } else { - return { - ttdHit: true, - }; } + return { + ttdHit: true, + }; } /** @@ -167,7 +166,6 @@ export class Eth1MergeBlockTracker { this.status = {code: StatusCode.SEARCHING}; this.logger.info("Starting search for terminal POW block", { - // eslint-disable-next-line @typescript-eslint/naming-convention TERMINAL_TOTAL_DIFFICULTY: this.config.TERMINAL_TOTAL_DIFFICULTY, }); @@ -194,7 +192,7 @@ export class Eth1MergeBlockTracker { // Persist found merge block here to affect both caller paths: // - internal searcher // - external caller if STOPPED - if (mergeBlock && this.status.code != StatusCode.FOUND) { + if (mergeBlock && this.status.code !== StatusCode.FOUND) { if (this.status.code === StatusCode.SEARCHING) { this.close(); } @@ -243,61 +241,56 @@ export class Eth1MergeBlockTracker { const block = await this.getPowBlock(terminalBlockHash); if (block) { return block; - } else { - // if a TERMINAL_BLOCK_HASH other than ZERO_HASH is configured and we can't find it, return NONE - return null; } + // if a TERMINAL_BLOCK_HASH other than ZERO_HASH is configured and we can't find it, return NONE + return null; } // Search merge block by TTD - else { - const latestBlockRaw = await this.eth1Provider.getBlockByNumber("latest"); - if (!latestBlockRaw) { - throw Error("getBlockByNumber('latest') returned null"); - } - - let block = toPowBlock(latestBlockRaw); - this.latestEth1Block = {...block, timestamp: quantityToNum(latestBlockRaw.timestamp)}; - this.cacheBlock(block); + const latestBlockRaw = await this.eth1Provider.getBlockByNumber("latest"); + if (!latestBlockRaw) { + throw Error("getBlockByNumber('latest') returned null"); + } - // This code path to look backwards for the merge block is only necessary if: - // - The network has not yet found the merge block - // - There are descendants of the merge block in the eth1 chain - // For the search below to require more than a few hops, multiple block proposers in a row must fail to detect - // an existing merge block. Such situation is extremely unlikely, so this search is left un-optimized. Since - // this class can start eagerly looking for the merge block when not necessary, startPollingMergeBlock() should - // only be called when there is certainty that a mergeBlock search is necessary. - - // eslint-disable-next-line no-constant-condition - while (true) { - if (block.totalDifficulty < this.config.TERMINAL_TOTAL_DIFFICULTY) { - // TTD not reached yet - return null; - } + let block = toPowBlock(latestBlockRaw); + this.latestEth1Block = {...block, timestamp: quantityToNum(latestBlockRaw.timestamp)}; + this.cacheBlock(block); + + // This code path to look backwards for the merge block is only necessary if: + // - The network has not yet found the merge block + // - There are descendants of the merge block in the eth1 chain + // For the search below to require more than a few hops, multiple block proposers in a row must fail to detect + // an existing merge block. Such situation is extremely unlikely, so this search is left un-optimized. Since + // this class can start eagerly looking for the merge block when not necessary, startPollingMergeBlock() should + // only be called when there is certainty that a mergeBlock search is necessary. + + while (true) { + if (block.totalDifficulty < this.config.TERMINAL_TOTAL_DIFFICULTY) { + // TTD not reached yet + return null; + } - // else block.totalDifficulty >= this.config.TERMINAL_TOTAL_DIFFICULTY - // Potential mergeBlock! Must find the first block that passes TTD + // else block.totalDifficulty >= this.config.TERMINAL_TOTAL_DIFFICULTY + // Potential mergeBlock! Must find the first block that passes TTD - // Allow genesis block to reach TTD https://github.com/ethereum/consensus-specs/pull/2719 - if (block.parentHash === ZERO_HASH_HEX) { - return block; - } + // Allow genesis block to reach TTD https://github.com/ethereum/consensus-specs/pull/2719 + if (block.parentHash === ZERO_HASH_HEX) { + return block; + } - const parent = await this.getPowBlock(block.parentHash); - if (!parent) { - throw Error(`Unknown parent of block with TD>TTD ${block.parentHash}`); - } + const parent = await this.getPowBlock(block.parentHash); + if (!parent) { + throw Error(`Unknown parent of block with TD>TTD ${block.parentHash}`); + } - this.metrics?.eth1.eth1ParentBlocksFetched.inc(); + this.metrics?.eth1.eth1ParentBlocksFetched.inc(); - // block.td > TTD && parent.td < TTD => block is mergeBlock - if (parent.totalDifficulty < this.config.TERMINAL_TOTAL_DIFFICULTY) { - // Is terminal total difficulty block AND has verified block -> parent relationship - return block; - } else { - block = parent; - } + // block.td > TTD && parent.td < TTD => block is mergeBlock + if (parent.totalDifficulty < this.config.TERMINAL_TOTAL_DIFFICULTY) { + // Is terminal total difficulty block AND has verified block -> parent relationship + return block; } + block = parent; } } diff --git a/packages/beacon-node/src/eth1/index.ts b/packages/beacon-node/src/eth1/index.ts index a8ba55c54141..42b82d03a848 100644 --- a/packages/beacon-node/src/eth1/index.ts +++ b/packages/beacon-node/src/eth1/index.ts @@ -1,6 +1,6 @@ -import {fromHexString} from "@chainsafe/ssz"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Root} from "@lodestar/types"; +import {fromHex} from "@lodestar/utils"; import {IEth1ForBlockProduction, Eth1DataAndDeposits, IEth1Provider, PowMergeBlock, TDProgress} from "./interface.js"; import {Eth1DepositDataTracker, Eth1DepositDataTrackerModules} from "./eth1DepositDataTracker.js"; import {Eth1MergeBlockTracker, Eth1MergeBlockTrackerModules} from "./eth1MergeBlockTracker.js"; @@ -53,9 +53,8 @@ export function initializeEth1ForBlockProduction( logger: modules.logger, signal: modules.signal, }); - } else { - return new Eth1ForBlockProductionDisabled(); } + return new Eth1ForBlockProductionDisabled(); } export class Eth1ForBlockProduction implements IEth1ForBlockProduction { @@ -85,14 +84,13 @@ export class Eth1ForBlockProduction implements IEth1ForBlockProduction { async getEth1DataAndDeposits(state: CachedBeaconStateAllForks): Promise { if (this.eth1DepositDataTracker === null) { return {eth1Data: state.eth1Data, deposits: []}; - } else { - return this.eth1DepositDataTracker.getEth1DataAndDeposits(state); } + return this.eth1DepositDataTracker.getEth1DataAndDeposits(state); } async getTerminalPowBlock(): Promise { const block = await this.eth1MergeBlockTracker.getTerminalPowBlock(); - return block && fromHexString(block.blockHash); + return block && fromHex(block.blockHash); } getPowBlock(powBlockHash: string): Promise { @@ -104,11 +102,11 @@ export class Eth1ForBlockProduction implements IEth1ForBlockProduction { } startPollingMergeBlock(): void { - return this.eth1MergeBlockTracker.startPollingMergeBlock(); + this.eth1MergeBlockTracker.startPollingMergeBlock(); } stopPollingEth1Data(): void { - return this.eth1DepositDataTracker?.stopPollingEth1Data(); + this.eth1DepositDataTracker?.stopPollingEth1Data(); } } diff --git a/packages/beacon-node/src/eth1/provider/eth1Provider.ts b/packages/beacon-node/src/eth1/provider/eth1Provider.ts index 3af909cd132e..d284700ddb1d 100644 --- a/packages/beacon-node/src/eth1/provider/eth1Provider.ts +++ b/packages/beacon-node/src/eth1/provider/eth1Provider.ts @@ -1,7 +1,6 @@ -import {toHexString} from "@chainsafe/ssz"; import {phase0} from "@lodestar/types"; import {ChainConfig} from "@lodestar/config"; -import {fromHex, isErrorAborted, createElapsedTimeTracker, toPrintableUrl} from "@lodestar/utils"; +import {fromHex, isErrorAborted, createElapsedTimeTracker, toPrintableUrl, toHex} from "@lodestar/utils"; import {Logger} from "@lodestar/logger"; import {FetchError, isFetchError} from "@lodestar/api"; @@ -22,8 +21,6 @@ import { } from "./jsonRpcHttpClient.js"; import {isJsonRpcTruncatedError, quantityToNum, numToQuantity, dataToBytes} from "./utils.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - /** * Binds return types to Ethereum JSON RPC methods */ @@ -72,7 +69,7 @@ export class Eth1Provider implements IEth1Provider { ) { this.logger = opts.logger; this.deployBlock = opts.depositContractDeployBlock ?? 0; - this.depositContractAddress = toHexString(config.DEPOSIT_CONTRACT_ADDRESS); + this.depositContractAddress = toHex(config.DEPOSIT_CONTRACT_ADDRESS); const providerUrls = opts.providerUrls ?? DEFAULT_PROVIDER_URLS; this.rpc = new JsonRpcHttpClient(providerUrls, { diff --git a/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts b/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts index cbe70ecb7e18..f2ceae0d8c19 100644 --- a/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts +++ b/packages/beacon-node/src/eth1/provider/jsonRpcHttpClient.ts @@ -1,4 +1,4 @@ -import {EventEmitter} from "events"; +import {EventEmitter} from "node:events"; import {StrictEventEmitter} from "strict-event-emitter-types"; import {fetch} from "@lodestar/api"; import {ErrorAborted, Gauge, Histogram, TimeoutError, isValidHttpUrl, retry} from "@lodestar/utils"; @@ -268,7 +268,7 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient { }; const token = encodeJwtToken(jwtClaim, this.jwtSecret); - headers["Authorization"] = `Bearer ${token}`; + headers.Authorization = `Bearer ${token}`; } const res = await fetch(url, { @@ -296,12 +296,10 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient { // controller will abort on both parent signal abort + timeout of this specific request if (this.opts?.signal?.aborted) { throw new ErrorAborted("request"); - } else { - throw new TimeoutError("request"); } - } else { - throw e; + throw new TimeoutError("request"); } + throw e; } finally { timer?.(); this.metrics?.activeRequests.dec({routeId}, 1); @@ -315,11 +313,11 @@ export class JsonRpcHttpClient implements IJsonRpcHttpClient { function parseRpcResponse(res: RpcResponse, payload: RpcPayload

): R { if (res.result !== undefined) { return res.result; - } else if (res.error !== undefined) { + } + if (res.error !== undefined) { throw new ErrorJsonRpcResponse(res, payload.method); - } else { - throw Error(`Invalid JSON RPC response, no result or error property: ${jsonSerializeTry(res)}`); } + throw Error(`Invalid JSON RPC response, no result or error property: ${jsonSerializeTry(res)}`); } /** diff --git a/packages/beacon-node/src/eth1/provider/jwt.ts b/packages/beacon-node/src/eth1/provider/jwt.ts index da1fc1827cdd..1e267120957f 100644 --- a/packages/beacon-node/src/eth1/provider/jwt.ts +++ b/packages/beacon-node/src/eth1/provider/jwt.ts @@ -2,7 +2,6 @@ import type {TAlgorithm} from "jwt-simple"; // TODO: fix jwt-simple types import jwt from "jwt-simple"; -// eslint-disable-next-line import/no-named-as-default-member const {encode, decode} = jwt; /** diff --git a/packages/beacon-node/src/eth1/provider/utils.ts b/packages/beacon-node/src/eth1/provider/utils.ts index 506e4e48711a..7010e1377ca6 100644 --- a/packages/beacon-node/src/eth1/provider/utils.ts +++ b/packages/beacon-node/src/eth1/provider/utils.ts @@ -1,6 +1,5 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; import {RootHex} from "@lodestar/types"; -import {bytesToBigInt, bigIntToBytes} from "@lodestar/utils"; +import {bytesToBigInt, bigIntToBytes, toHex, fromHex} from "@lodestar/utils"; import {ErrorParseJson} from "./jsonRpcHttpClient.js"; /** QUANTITY as defined in ethereum execution layer JSON RPC https://eth.wiki/json-rpc/API */ @@ -32,7 +31,7 @@ export function bytesToHex(bytes: Uint8Array): string { return "0x" + bytes[0].toString(16); } - return toHexString(bytes); + return toHex(bytes); } /** @@ -54,7 +53,7 @@ export function numToQuantity(num: number | bigint): QUANTITY { */ export function quantityToNum(hex: QUANTITY, id = ""): number { const num = parseInt(hex, 16); - if (isNaN(num) || num < 0) throw Error(`Invalid hex decimal ${id} '${hex}'`); + if (Number.isNaN(num) || num < 0) throw Error(`Invalid hex decimal ${id} '${hex}'`); return num; } @@ -100,7 +99,7 @@ export function bytesToQuantity(bytes: Uint8Array): QUANTITY { * - WRONG: 004200 (must be prefixed 0x) */ export function bytesToData(bytes: Uint8Array): DATA { - return toHexString(bytes); + return toHex(bytes); } /** @@ -108,7 +107,7 @@ export function bytesToData(bytes: Uint8Array): DATA { */ export function dataToBytes(hex: DATA, fixedLength: number | null): Uint8Array { try { - const bytes = fromHexString(hex); + const bytes = fromHex(hex); if (fixedLength != null && bytes.length !== fixedLength) { throw Error(`Wrong data length ${bytes.length} expected ${fixedLength}`); } diff --git a/packages/beacon-node/src/eth1/utils/depositContract.ts b/packages/beacon-node/src/eth1/utils/depositContract.ts index 7247fe8cebaf..b576a3d5f61c 100644 --- a/packages/beacon-node/src/eth1/utils/depositContract.ts +++ b/packages/beacon-node/src/eth1/utils/depositContract.ts @@ -1,6 +1,6 @@ import {Interface} from "@ethersproject/abi"; -import {fromHexString} from "@chainsafe/ssz"; import {phase0, ssz} from "@lodestar/types"; +import {fromHex} from "@lodestar/utils"; const depositEventFragment = "event DepositEvent(bytes pubkey, bytes withdrawal_credentials, bytes amount, bytes signature, bytes index)"; @@ -23,15 +23,15 @@ export function parseDepositLog(log: {blockNumber: number; data: string; topics: blockNumber: log.blockNumber, index: parseHexNumLittleEndian(values.index), depositData: { - pubkey: fromHexString(values.pubkey), - withdrawalCredentials: fromHexString(values.withdrawal_credentials), + pubkey: fromHex(values.pubkey), + withdrawalCredentials: fromHex(values.withdrawal_credentials), amount: parseHexNumLittleEndian(values.amount), - signature: fromHexString(values.signature), + signature: fromHex(values.signature), }, }; } function parseHexNumLittleEndian(hex: string): number { // Can't use parseInt() because amount is a hex string in little endian - return ssz.UintNum64.deserialize(fromHexString(hex)); + return ssz.UintNum64.deserialize(fromHex(hex)); } diff --git a/packages/beacon-node/src/eth1/utils/deposits.ts b/packages/beacon-node/src/eth1/utils/deposits.ts index 8d0331fc01d6..36f8c331ebc9 100644 --- a/packages/beacon-node/src/eth1/utils/deposits.ts +++ b/packages/beacon-node/src/eth1/utils/deposits.ts @@ -33,7 +33,9 @@ export async function getDeposits( if (deposits.length < depositsLen) { throw new Eth1Error({code: Eth1ErrorCode.NOT_ENOUGH_DEPOSITS, len: deposits.length, expectedLen: depositsLen}); - } else if (deposits.length > depositsLen) { + } + + if (deposits.length > depositsLen) { throw new Eth1Error({code: Eth1ErrorCode.TOO_MANY_DEPOSITS, len: deposits.length, expectedLen: depositsLen}); } diff --git a/packages/beacon-node/src/eth1/utils/eth1Vote.ts b/packages/beacon-node/src/eth1/utils/eth1Vote.ts index cdc8fa04163e..84d35ae7d434 100644 --- a/packages/beacon-node/src/eth1/utils/eth1Vote.ts +++ b/packages/beacon-node/src/eth1/utils/eth1Vote.ts @@ -72,16 +72,14 @@ export function pickEth1Vote(state: BeaconStateAllForks, votesToConsider: phase0 } // If there's a single winning vote with a majority vote that one - else if (eth1DataRootsMaxVotes.length === 1) { + if (eth1DataRootsMaxVotes.length === 1) { return eth1DataHashToEth1Data.get(eth1DataRootsMaxVotes[0]) ?? state.eth1Data; } // If there are multiple winning votes, vote for the latest one - else { - const latestMostVotedRoot = - eth1DataVotesOrder[Math.max(...eth1DataRootsMaxVotes.map((root) => eth1DataVotesOrder.indexOf(root)))]; - return eth1DataHashToEth1Data.get(latestMostVotedRoot) ?? state.eth1Data; - } + const latestMostVotedRoot = + eth1DataVotesOrder[Math.max(...eth1DataRootsMaxVotes.map((root) => eth1DataVotesOrder.indexOf(root)))]; + return eth1DataHashToEth1Data.get(latestMostVotedRoot) ?? state.eth1Data; } /** @@ -120,7 +118,6 @@ function getKeysWithMaxValue(map: Map): T[] { * ✓ pickEth1Vote - max votes 37.89912 ops/s 26.38583 ms/op - 29 runs 1.27 s */ function getEth1DataKey(eth1Data: phase0.Eth1Data): string { - // return toHexString(ssz.phase0.Eth1Data.hashTreeRoot(eth1Data)); return fastSerializeEth1Data(eth1Data); } diff --git a/packages/beacon-node/src/eth1/utils/optimizeNextBlockDiffForGenesis.ts b/packages/beacon-node/src/eth1/utils/optimizeNextBlockDiffForGenesis.ts index 4dc633693580..961d58680e47 100644 --- a/packages/beacon-node/src/eth1/utils/optimizeNextBlockDiffForGenesis.ts +++ b/packages/beacon-node/src/eth1/utils/optimizeNextBlockDiffForGenesis.ts @@ -13,7 +13,6 @@ export function optimizeNextBlockDiffForGenesis( const numBlocksToGenesis = Math.floor(timeToGenesis / params.SECONDS_PER_ETH1_BLOCK); if (numBlocksToGenesis <= 2) { return 1; - } else { - return Math.max(1, Math.floor(numBlocksToGenesis / 2)); } + return Math.max(1, Math.floor(numBlocksToGenesis / 2)); } diff --git a/packages/beacon-node/src/execution/builder/http.ts b/packages/beacon-node/src/execution/builder/http.ts index 934b874f5ae3..b95cfd6a80b9 100644 --- a/packages/beacon-node/src/execution/builder/http.ts +++ b/packages/beacon-node/src/execution/builder/http.ts @@ -8,6 +8,8 @@ import { SignedBeaconBlockOrContents, SignedBlindedBeaconBlock, ExecutionPayloadHeader, + electra, + WithOptionalBytes, } from "@lodestar/types"; import {parseExecutionPayloadAndBlobsBundle, reconstructFullBlockOrContents} from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; @@ -17,6 +19,7 @@ import {SLOTS_PER_EPOCH, ForkExecution} from "@lodestar/params"; import {toPrintableUrl} from "@lodestar/utils"; import {Metrics} from "../../metrics/metrics.js"; import {IExecutionBuilder} from "./interface.js"; +import {WireFormat} from "@lodestar/api"; export type ExecutionBuilderHttpOpts = { enabled: boolean; @@ -37,6 +40,23 @@ export const defaultExecutionBuilderHttpOpts: ExecutionBuilderHttpOpts = { timeout: 12000, }; +/** + * Expected error if builder does not provide a bid. Most of the time, this + * is due to `min-bid` setting on the mev-boost side but in rare cases could + * also happen if there are no bids from any of the connected relayers. + */ +export class NoBidReceived extends Error { + constructor() { + super("No bid received"); + } +} + +/** + * Duration given to the builder to provide a `SignedBuilderBid` before the deadline + * is reached, aborting the external builder flow in favor of the local build process. + */ +const BUILDER_PROPOSAL_DELAY_TOLERANCE = 1000; + export class ExecutionBuilderHttp implements IExecutionBuilder { readonly api: BuilderApi; readonly config: ChainForkConfig; @@ -46,6 +66,13 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { faultInspectionWindow: number; allowedFaults: number; + /** + * Determine if SSZ is supported by requesting an SSZ encoded response in the `getHeader` request. + * The builder responding with a SSZ serialized `SignedBuilderBid` indicates support to handle the + * `SignedBlindedBeaconBlock` as SSZ serialized bytes instead of JSON when calling `submitBlindedBlock`. + */ + private sszSupported = false; + constructor( opts: ExecutionBuilderHttpOpts, config: ChainForkConfig, @@ -62,7 +89,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { headers: opts.userAgent ? {"User-Agent": opts.userAgent} : undefined, }, }, - {config, metrics: metrics?.builderHttpClient} + {config, metrics: metrics?.builderHttpClient, logger} ); logger?.info("External builder", {url: toPrintableUrl(baseUrl)}); this.config = config; @@ -114,22 +141,35 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { header: ExecutionPayloadHeader; executionPayloadValue: Wei; blobKzgCommitments?: deneb.BlobKzgCommitments; + executionRequests?: electra.ExecutionRequests; }> { - const signedBuilderBid = (await this.api.getHeader({slot, parentHash, proposerPubkey})).value(); + const res = await this.api.getHeader( + {slot, parentHash, proposerPubkey}, + {timeoutMs: BUILDER_PROPOSAL_DELAY_TOLERANCE} + ); + const signedBuilderBid = res.value(); if (!signedBuilderBid) { - throw Error("No bid received"); + throw new NoBidReceived(); } + this.sszSupported = res.wireFormat() === WireFormat.ssz; + const {header, value: executionPayloadValue} = signedBuilderBid.message; const {blobKzgCommitments} = signedBuilderBid.message as deneb.BuilderBid; - return {header, executionPayloadValue, blobKzgCommitments}; + const {executionRequests} = signedBuilderBid.message as electra.BuilderBid; + return {header, executionPayloadValue, blobKzgCommitments, executionRequests}; } - async submitBlindedBlock(signedBlindedBlock: SignedBlindedBeaconBlock): Promise { - const data = (await this.api.submitBlindedBlock({signedBlindedBlock}, {retries: 2})).value(); + async submitBlindedBlock( + signedBlindedBlock: WithOptionalBytes + ): Promise { + const res = await this.api.submitBlindedBlock( + {signedBlindedBlock}, + {retries: 2, requestWireFormat: this.sszSupported ? WireFormat.ssz : WireFormat.json} + ); - const {executionPayload, blobsBundle} = parseExecutionPayloadAndBlobsBundle(data); + const {executionPayload, blobsBundle} = parseExecutionPayloadAndBlobsBundle(res.value()); // for the sake of timely proposals we can skip matching the payload with payloadHeader // if the roots (transactions, withdrawals) don't match, this will likely lead to a block with @@ -137,6 +177,6 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { // probably need diagonis if this block turns out to be invalid because of some bug // const contents = blobsBundle ? {blobs: blobsBundle.blobs, kzgProofs: blobsBundle.proofs} : null; - return reconstructFullBlockOrContents(signedBlindedBlock, {executionPayload, contents}); + return reconstructFullBlockOrContents(signedBlindedBlock.data, {executionPayload, contents}); } } diff --git a/packages/beacon-node/src/execution/builder/index.ts b/packages/beacon-node/src/execution/builder/index.ts index 2f584ad7dadd..fff66a3e8bc5 100644 --- a/packages/beacon-node/src/execution/builder/index.ts +++ b/packages/beacon-node/src/execution/builder/index.ts @@ -18,6 +18,7 @@ export function initializeExecutionBuilder( ): IExecutionBuilder { switch (opts.mode) { case "http": + return new ExecutionBuilderHttp(opts, config, metrics, logger); default: return new ExecutionBuilderHttp(opts, config, metrics, logger); } diff --git a/packages/beacon-node/src/execution/builder/interface.ts b/packages/beacon-node/src/execution/builder/interface.ts index 9a655a68de02..19a935a9bf7e 100644 --- a/packages/beacon-node/src/execution/builder/interface.ts +++ b/packages/beacon-node/src/execution/builder/interface.ts @@ -8,6 +8,8 @@ import { SignedBeaconBlockOrContents, ExecutionPayloadHeader, SignedBlindedBeaconBlock, + electra, + WithOptionalBytes, } from "@lodestar/types"; import {ForkExecution} from "@lodestar/params"; @@ -36,6 +38,9 @@ export interface IExecutionBuilder { header: ExecutionPayloadHeader; executionPayloadValue: Wei; blobKzgCommitments?: deneb.BlobKzgCommitments; + executionRequests?: electra.ExecutionRequests; }>; - submitBlindedBlock(signedBlock: SignedBlindedBeaconBlock): Promise; + submitBlindedBlock( + signedBlindedBlock: WithOptionalBytes + ): Promise; } diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index a69a5b94bd65..e5e65746d90f 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -1,4 +1,4 @@ -import {ExecutionPayload, Root, RootHex, Wei} from "@lodestar/types"; +import {ExecutionPayload, ExecutionRequests, Root, RootHex, Wei} from "@lodestar/types"; import {SLOTS_PER_EPOCH, ForkName, ForkSeq} from "@lodestar/params"; import {Logger} from "@lodestar/logger"; import { @@ -37,6 +37,7 @@ import { ExecutionPayloadBody, assertReqSizeLimit, deserializeExecutionPayloadBody, + serializeExecutionRequests, } from "./types.js"; import {getExecutionEngineState} from "./utils.js"; @@ -195,7 +196,8 @@ export class ExecutionEngineHttp implements IExecutionEngine { fork: ForkName, executionPayload: ExecutionPayload, versionedHashes?: VersionedHashes, - parentBlockRoot?: Root + parentBlockRoot?: Root, + executionRequests?: ExecutionRequests ): Promise { const method = ForkSeq[fork] >= ForkSeq.electra @@ -220,12 +222,28 @@ export class ExecutionEngineHttp implements IExecutionEngine { const serializedVersionedHashes = serializeVersionedHashes(versionedHashes); const parentBeaconBlockRoot = serializeBeaconBlockRoot(parentBlockRoot); - const method = ForkSeq[fork] >= ForkSeq.electra ? "engine_newPayloadV4" : "engine_newPayloadV3"; - engineRequest = { - method, - params: [serializedExecutionPayload, serializedVersionedHashes, parentBeaconBlockRoot], - methodOpts: notifyNewPayloadOpts, - }; + if (ForkSeq[fork] >= ForkSeq.electra) { + if (executionRequests === undefined) { + throw Error(`executionRequests required in notifyNewPayload for fork=${fork}`); + } + const serializedExecutionRequests = serializeExecutionRequests(executionRequests); + engineRequest = { + method: "engine_newPayloadV4", + params: [ + serializedExecutionPayload, + serializedVersionedHashes, + parentBeaconBlockRoot, + serializedExecutionRequests, + ], + methodOpts: notifyNewPayloadOpts, + }; + } else { + engineRequest = { + method: "engine_newPayloadV3", + params: [serializedExecutionPayload, serializedVersionedHashes, parentBeaconBlockRoot], + methodOpts: notifyNewPayloadOpts, + }; + } } else { const method = ForkSeq[fork] >= ForkSeq.capella ? "engine_newPayloadV2" : "engine_newPayloadV1"; engineRequest = { @@ -240,9 +258,8 @@ export class ExecutionEngineHttp implements IExecutionEngine { ).catch((e: Error) => { if (e instanceof HttpRpcError || e instanceof ErrorJsonRpcResponse) { return {status: ExecutionPayloadStatus.ELERROR, latestValidHash: null, validationError: e.message}; - } else { - return {status: ExecutionPayloadStatus.UNAVAILABLE, latestValidHash: null, validationError: e.message}; } + return {status: ExecutionPayloadStatus.UNAVAILABLE, latestValidHash: null, validationError: e.message}; }); this.updateEngineState(getExecutionEngineState({payloadStatus: status, oldState: this.state})); @@ -361,9 +378,8 @@ export class ExecutionEngineHttp implements IExecutionEngine { // Throw error on syncing if requested to produce a block, else silently ignore if (payloadAttributes) { throw Error("Execution Layer Syncing"); - } else { - return null; } + return null; case ExecutionPayloadStatus.INVALID: throw Error( @@ -391,6 +407,7 @@ export class ExecutionEngineHttp implements IExecutionEngine { executionPayload: ExecutionPayload; executionPayloadValue: Wei; blobsBundle?: BlobsBundle; + executionRequests?: ExecutionRequests; shouldOverrideBuilder?: boolean; }> { const method = @@ -418,9 +435,8 @@ export class ExecutionEngineHttp implements IExecutionEngine { this.payloadIdCache.prune(); } - async getPayloadBodiesByHash(fork: ForkName, blockHashes: RootHex[]): Promise<(ExecutionPayloadBody | null)[]> { - const method = - ForkSeq[fork] >= ForkSeq.electra ? "engine_getPayloadBodiesByHashV2" : "engine_getPayloadBodiesByHashV1"; + async getPayloadBodiesByHash(_fork: ForkName, blockHashes: RootHex[]): Promise<(ExecutionPayloadBody | null)[]> { + const method = "engine_getPayloadBodiesByHashV1"; assertReqSizeLimit(blockHashes.length, 32); const response = await this.rpc.fetchWithRetries< EngineApiRpcReturnTypes[typeof method], @@ -430,12 +446,11 @@ export class ExecutionEngineHttp implements IExecutionEngine { } async getPayloadBodiesByRange( - fork: ForkName, + _fork: ForkName, startBlockNumber: number, blockCount: number ): Promise<(ExecutionPayloadBody | null)[]> { - const method = - ForkSeq[fork] >= ForkSeq.electra ? "engine_getPayloadBodiesByRangeV2" : "engine_getPayloadBodiesByRangeV1"; + const method = "engine_getPayloadBodiesByRangeV1"; assertReqSizeLimit(blockCount, 32); const start = numToQuantity(startBlockNumber); const count = numToQuantity(blockCount); diff --git a/packages/beacon-node/src/execution/engine/index.ts b/packages/beacon-node/src/execution/engine/index.ts index a8b878502aff..dd2de2017c22 100644 --- a/packages/beacon-node/src/execution/engine/index.ts +++ b/packages/beacon-node/src/execution/engine/index.ts @@ -55,6 +55,8 @@ export function initializeExecutionEngine( return getExecutionEngineFromBackend(new ExecutionEngineMockBackend(opts), modules); case "http": + return getExecutionEngineHttp(opts, modules); + default: return getExecutionEngineHttp(opts, modules); } diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index 5226a46ac720..e6f9cfee526b 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -1,12 +1,12 @@ import {ForkName} from "@lodestar/params"; import {KZGCommitment, Blob, KZGProof} from "@lodestar/types/deneb"; -import {Root, RootHex, capella, Wei, ExecutionPayload} from "@lodestar/types"; +import {Root, RootHex, capella, Wei, ExecutionPayload, ExecutionRequests} from "@lodestar/types"; import {DATA} from "../../eth1/provider/utils.js"; -import {PayloadIdCache, PayloadId, WithdrawalV1, DepositRequestV1} from "./payloadIdCache.js"; +import {PayloadIdCache, PayloadId, WithdrawalV1} from "./payloadIdCache.js"; import {ExecutionPayloadBody} from "./types.js"; -export {PayloadIdCache, type PayloadId, type WithdrawalV1, type DepositRequestV1}; +export {PayloadIdCache, type PayloadId, type WithdrawalV1}; export enum ExecutionPayloadStatus { /** given payload is valid */ @@ -134,7 +134,8 @@ export interface IExecutionEngine { fork: ForkName, executionPayload: ExecutionPayload, versionedHashes?: VersionedHashes, - parentBeaconBlockRoot?: Root + parentBeaconBlockRoot?: Root, + executionRequests?: ExecutionRequests ): Promise; /** @@ -171,6 +172,7 @@ export interface IExecutionEngine { executionPayload: ExecutionPayload; executionPayloadValue: Wei; blobsBundle?: BlobsBundle; + executionRequests?: ExecutionRequests; shouldOverrideBuilder?: boolean; }>; diff --git a/packages/beacon-node/src/execution/engine/mock.ts b/packages/beacon-node/src/execution/engine/mock.ts index 9fed781fa523..8062b68bf572 100644 --- a/packages/beacon-node/src/execution/engine/mock.ts +++ b/packages/beacon-node/src/execution/engine/mock.ts @@ -85,7 +85,6 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { }); this.handlers = { - /* eslint-disable @typescript-eslint/naming-convention */ engine_newPayloadV1: this.notifyNewPayload.bind(this), engine_newPayloadV2: this.notifyNewPayload.bind(this), engine_newPayloadV3: this.notifyNewPayload.bind(this), @@ -98,10 +97,8 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { engine_getPayloadV3: this.getPayload.bind(this), engine_getPayloadV4: this.getPayload.bind(this), engine_getPayloadBodiesByHashV1: this.getPayloadBodiesByHash.bind(this), - engine_getPayloadBodiesByHashV2: this.getPayloadBodiesByHash.bind(this), engine_getPayloadBodiesByRangeV1: this.getPayloadBodiesByRange.bind(this), engine_getClientVersionV1: this.getClientVersionV1.bind(this), - engine_getPayloadBodiesByRangeV2: this.getPayloadBodiesByRange.bind(this), }; } @@ -152,7 +149,9 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { const predefinedResponse = this.predefinedPayloadStatuses.get(blockHash); if (predefinedResponse) { return predefinedResponse; - } else if (this.opts.onlyPredefinedResponses) { + } + + if (this.opts.onlyPredefinedResponses) { throw Error(`No predefined response for blockHash ${blockHash}`); } @@ -215,7 +214,9 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { payloadStatus: predefinedResponse, payloadId: null, }; - } else if (this.opts.onlyPredefinedResponses) { + } + + if (this.opts.onlyPredefinedResponses) { throw Error(`No predefined response for headBlockHash ${headBlockHash}`); } @@ -347,14 +348,12 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { } // Don't start build process - else { - // IF the payload is deemed VALID and a build process hasn't been started - // {payloadStatus: {status: VALID, latestValidHash: forkchoiceState.headBlockHash, validationError: null}, payloadId: null} - return { - payloadStatus: {status: ExecutionPayloadStatus.VALID, latestValidHash: null, validationError: null}, - payloadId: null, - }; - } + // IF the payload is deemed VALID and a build process hasn't been started + // {payloadStatus: {status: VALID, latestValidHash: forkchoiceState.headBlockHash, validationError: null}, payloadId: null} + return { + payloadStatus: {status: ExecutionPayloadStatus.VALID, latestValidHash: null, validationError: null}, + payloadId: null, + }; } /** diff --git a/packages/beacon-node/src/execution/engine/payloadIdCache.ts b/packages/beacon-node/src/execution/engine/payloadIdCache.ts index 005a1ef14322..ea37e0922e9c 100644 --- a/packages/beacon-node/src/execution/engine/payloadIdCache.ts +++ b/packages/beacon-node/src/execution/engine/payloadIdCache.ts @@ -18,26 +18,6 @@ export type WithdrawalV1 = { amount: QUANTITY; }; -export type DepositRequestV1 = { - pubkey: DATA; - withdrawalCredentials: DATA; - amount: QUANTITY; - signature: DATA; - index: QUANTITY; -}; - -export type WithdrawalRequestV1 = { - sourceAddress: DATA; - validatorPubkey: DATA; - amount: QUANTITY; -}; - -export type ConsolidationRequestV1 = { - sourceAddress: DATA; - sourcePubkey: DATA; - targetPubkey: DATA; -}; - type FcuAttributes = {headBlockHash: DATA; finalizedBlockHash: DATA} & Omit; export class PayloadIdCache { diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index 63cb4da88b6c..52ddb8548629 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -1,4 +1,4 @@ -import {capella, deneb, electra, Wei, bellatrix, Root, ExecutionPayload} from "@lodestar/types"; +import {capella, deneb, electra, Wei, bellatrix, Root, ExecutionPayload, ExecutionRequests, ssz} from "@lodestar/types"; import { BYTES_PER_LOGS_BLOOM, FIELD_ELEMENTS_PER_BLOB, @@ -17,9 +17,7 @@ import { quantityToBigint, } from "../../eth1/provider/utils.js"; import {ExecutionPayloadStatus, BlobsBundle, PayloadAttributes, VersionedHashes} from "./interface.js"; -import {WithdrawalV1, DepositRequestV1, WithdrawalRequestV1, ConsolidationRequestV1} from "./payloadIdCache.js"; - -/* eslint-disable @typescript-eslint/naming-convention */ +import {WithdrawalV1} from "./payloadIdCache.js"; export type EngineApiRpcParamTypes = { /** @@ -28,7 +26,7 @@ export type EngineApiRpcParamTypes = { engine_newPayloadV1: [ExecutionPayloadRpc]; engine_newPayloadV2: [ExecutionPayloadRpc]; engine_newPayloadV3: [ExecutionPayloadRpc, VersionedHashesRpc, DATA]; - engine_newPayloadV4: [ExecutionPayloadRpc, VersionedHashesRpc, DATA]; + engine_newPayloadV4: [ExecutionPayloadRpc, VersionedHashesRpc, DATA, ExecutionRequestsRpc]; /** * 1. Object - Payload validity status with respect to the consensus rules: * - blockHash: DATA, 32 Bytes - block hash value of the payload @@ -58,7 +56,6 @@ export type EngineApiRpcParamTypes = { * 1. Array of DATA - Array of block_hash field values of the ExecutionPayload structure * */ engine_getPayloadBodiesByHashV1: DATA[][]; - engine_getPayloadBodiesByHashV2: DATA[][]; /** * 1. start: QUANTITY, 64 bits - Starting block number @@ -70,7 +67,6 @@ export type EngineApiRpcParamTypes = { * Object - Instance of ClientVersion */ engine_getClientVersionV1: [ClientVersionRpc]; - engine_getPayloadBodiesByRangeV2: [start: QUANTITY, count: QUANTITY]; }; export type PayloadStatus = { @@ -109,12 +105,10 @@ export type EngineApiRpcReturnTypes = { engine_getPayloadV4: ExecutionPayloadResponse; engine_getPayloadBodiesByHashV1: (ExecutionPayloadBodyRpc | null)[]; - engine_getPayloadBodiesByHashV2: (ExecutionPayloadBodyRpc | null)[]; engine_getPayloadBodiesByRangeV1: (ExecutionPayloadBodyRpc | null)[]; engine_getClientVersionV1: ClientVersionRpc[]; - engine_getPayloadBodiesByRangeV2: (ExecutionPayloadBodyRpc | null)[]; }; type ExecutionPayloadRpcWithValue = { @@ -122,6 +116,7 @@ type ExecutionPayloadRpcWithValue = { // even though CL tracks this as executionPayloadValue, EL returns this as blockValue blockValue: QUANTITY; blobsBundle?: BlobsBundleRpc; + executionRequests?: ExecutionRequestsRpc; shouldOverrideBuilder?: boolean; }; type ExecutionPayloadResponse = ExecutionPayloadRpc | ExecutionPayloadRpcWithValue; @@ -129,19 +124,11 @@ type ExecutionPayloadResponse = ExecutionPayloadRpc | ExecutionPayloadRpcWithVal export type ExecutionPayloadBodyRpc = { transactions: DATA[]; withdrawals: WithdrawalV1[] | null | undefined; - // currently there is a discepancy between EL and CL field name references for deposit requests - // its likely CL receipt will be renamed to requests - depositRequests: DepositRequestV1[] | null | undefined; - withdrawalRequests: WithdrawalRequestV1[] | null | undefined; - consolidationRequests: ConsolidationRequestV1[] | null | undefined; }; export type ExecutionPayloadBody = { transactions: bellatrix.Transaction[]; withdrawals: capella.Withdrawals | null; - depositRequests: electra.DepositRequests | null; - withdrawalRequests: electra.WithdrawalRequests | null; - consolidationRequests: electra.ConsolidationRequests | null; }; export type ExecutionPayloadRpc = { @@ -163,9 +150,6 @@ export type ExecutionPayloadRpc = { blobGasUsed?: QUANTITY; // DENEB excessBlobGas?: QUANTITY; // DENEB parentBeaconBlockRoot?: QUANTITY; // DENEB - depositRequests?: DepositRequestRpc[]; // ELECTRA - withdrawalRequests?: WithdrawalRequestRpc[]; // ELECTRA - consolidationRequests?: ConsolidationRequestRpc[]; // ELECTRA }; export type WithdrawalRpc = { @@ -175,9 +159,17 @@ export type WithdrawalRpc = { amount: QUANTITY; }; -export type DepositRequestRpc = DepositRequestV1; -export type WithdrawalRequestRpc = WithdrawalRequestV1; -export type ConsolidationRequestRpc = ConsolidationRequestV1; +/** + * ExecutionRequestsRpc only holds 3 elements in the following order: + * - ssz'ed DepositRequests + * - ssz'ed WithdrawalRequests + * - ssz'ed ConsolidationRequests + */ +export type ExecutionRequestsRpc = [DepositRequestsRpc, WithdrawalRequestsRpc, ConsolidationRequestsRpc]; + +export type DepositRequestsRpc = DATA; +export type WithdrawalRequestsRpc = DATA; +export type ConsolidationRequestsRpc = DATA; export type VersionedHashesRpc = DATA[]; @@ -241,13 +233,7 @@ export function serializeExecutionPayload(fork: ForkName, data: ExecutionPayload payload.excessBlobGas = numToQuantity(excessBlobGas); } - // ELECTRA adds depositRequests/depositRequests to the ExecutionPayload - if (ForkSeq[fork] >= ForkSeq.electra) { - const {depositRequests, withdrawalRequests, consolidationRequests} = data as electra.ExecutionPayload; - payload.depositRequests = depositRequests.map(serializeDepositRequest); - payload.withdrawalRequests = withdrawalRequests.map(serializeWithdrawalRequest); - payload.consolidationRequests = consolidationRequests.map(serializeConsolidationRequest); - } + // No changes in Electra return payload; } @@ -267,23 +253,29 @@ export function parseExecutionPayload( executionPayload: ExecutionPayload; executionPayloadValue: Wei; blobsBundle?: BlobsBundle; + executionRequests?: ExecutionRequests; shouldOverrideBuilder?: boolean; } { let data: ExecutionPayloadRpc; let executionPayloadValue: Wei; let blobsBundle: BlobsBundle | undefined; + let executionRequests: ExecutionRequests | undefined; let shouldOverrideBuilder: boolean; if (hasPayloadValue(response)) { executionPayloadValue = quantityToBigint(response.blockValue); data = response.executionPayload; blobsBundle = response.blobsBundle ? parseBlobsBundle(response.blobsBundle) : undefined; + executionRequests = response.executionRequests + ? deserializeExecutionRequests(response.executionRequests) + : undefined; shouldOverrideBuilder = response.shouldOverrideBuilder ?? false; } else { data = response; // Just set it to zero as default executionPayloadValue = BigInt(0); blobsBundle = undefined; + executionRequests = undefined; shouldOverrideBuilder = false; } @@ -334,36 +326,9 @@ export function parseExecutionPayload( (executionPayload as deneb.ExecutionPayload).excessBlobGas = quantityToBigint(excessBlobGas); } - if (ForkSeq[fork] >= ForkSeq.electra) { - // electra adds depositRequests/depositRequests - const {depositRequests, withdrawalRequests, consolidationRequests} = data; - // Geth can also reply with null - if (depositRequests == null) { - throw Error( - `depositRequests missing for ${fork} >= electra executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}` - ); - } - (executionPayload as electra.ExecutionPayload).depositRequests = depositRequests.map(deserializeDepositRequest); - - if (withdrawalRequests == null) { - throw Error( - `withdrawalRequests missing for ${fork} >= electra executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}` - ); - } - (executionPayload as electra.ExecutionPayload).withdrawalRequests = - withdrawalRequests.map(deserializeWithdrawalRequest); - - if (consolidationRequests == null) { - throw Error( - `consolidationRequests missing for ${fork} >= electra executionPayload number=${executionPayload.blockNumber} hash=${data.blockHash}` - ); - } - (executionPayload as electra.ExecutionPayload).consolidationRequests = consolidationRequests.map( - deserializeConsolidationRequest - ); - } + // No changes in Electra - return {executionPayload, executionPayloadValue, blobsBundle, shouldOverrideBuilder}; + return {executionPayload, executionPayloadValue, blobsBundle, executionRequests, shouldOverrideBuilder}; } export function serializePayloadAttributes(data: PayloadAttributes): PayloadAttributesRpc { @@ -429,59 +394,53 @@ export function deserializeWithdrawal(serialized: WithdrawalRpc): capella.Withdr } as capella.Withdrawal; } -export function serializeDepositRequest(depositRequest: electra.DepositRequest): DepositRequestRpc { - return { - pubkey: bytesToData(depositRequest.pubkey), - withdrawalCredentials: bytesToData(depositRequest.withdrawalCredentials), - amount: numToQuantity(depositRequest.amount), - signature: bytesToData(depositRequest.signature), - index: numToQuantity(depositRequest.index), - }; +function serializeDepositRequests(depositRequests: electra.DepositRequests): DepositRequestsRpc { + return bytesToData(ssz.electra.DepositRequests.serialize(depositRequests)); } -export function deserializeDepositRequest(serialized: DepositRequestRpc): electra.DepositRequest { - return { - pubkey: dataToBytes(serialized.pubkey, 48), - withdrawalCredentials: dataToBytes(serialized.withdrawalCredentials, 32), - amount: quantityToNum(serialized.amount), - signature: dataToBytes(serialized.signature, 96), - index: quantityToNum(serialized.index), - } as electra.DepositRequest; +function deserializeDepositRequests(serialized: DepositRequestsRpc): electra.DepositRequests { + return ssz.electra.DepositRequests.deserialize(dataToBytes(serialized, null)); } -export function serializeWithdrawalRequest(withdrawalRequest: electra.WithdrawalRequest): WithdrawalRequestRpc { - return { - sourceAddress: bytesToData(withdrawalRequest.sourceAddress), - validatorPubkey: bytesToData(withdrawalRequest.validatorPubkey), - amount: numToQuantity(withdrawalRequest.amount), - }; +function serializeWithdrawalRequests(withdrawalRequests: electra.WithdrawalRequests): WithdrawalRequestsRpc { + return bytesToData(ssz.electra.WithdrawalRequests.serialize(withdrawalRequests)); } -export function deserializeWithdrawalRequest(withdrawalRequest: WithdrawalRequestRpc): electra.WithdrawalRequest { - return { - sourceAddress: dataToBytes(withdrawalRequest.sourceAddress, 20), - validatorPubkey: dataToBytes(withdrawalRequest.validatorPubkey, 48), - amount: quantityToNum(withdrawalRequest.amount), - }; +function deserializeWithdrawalRequest(serialized: WithdrawalRequestsRpc): electra.WithdrawalRequests { + return ssz.electra.WithdrawalRequests.deserialize(dataToBytes(serialized, null)); } -export function serializeConsolidationRequest( - consolidationRequest: electra.ConsolidationRequest -): ConsolidationRequestRpc { - return { - sourceAddress: bytesToData(consolidationRequest.sourceAddress), - sourcePubkey: bytesToData(consolidationRequest.sourcePubkey), - targetPubkey: bytesToData(consolidationRequest.targetPubkey), - }; +function serializeConsolidationRequests( + consolidationRequests: electra.ConsolidationRequests +): ConsolidationRequestsRpc { + return bytesToData(ssz.electra.ConsolidationRequests.serialize(consolidationRequests)); +} + +function deserializeConsolidationRequests(serialized: ConsolidationRequestsRpc): electra.ConsolidationRequests { + return ssz.electra.ConsolidationRequests.deserialize(dataToBytes(serialized, null)); } -export function deserializeConsolidationRequest( - consolidationRequest: ConsolidationRequestRpc -): electra.ConsolidationRequest { +/** + * This is identical to get_execution_requests_list in + * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/electra/beacon-chain.md#new-get_execution_requests_list + */ +export function serializeExecutionRequests(executionRequests: ExecutionRequests): ExecutionRequestsRpc { + const {deposits, withdrawals, consolidations} = executionRequests; + + return [ + serializeDepositRequests(deposits), + serializeWithdrawalRequests(withdrawals), + serializeConsolidationRequests(consolidations), + ]; +} + +export function deserializeExecutionRequests(serialized: ExecutionRequestsRpc): ExecutionRequests { + const [deposits, withdrawals, consolidations] = serialized; + return { - sourceAddress: dataToBytes(consolidationRequest.sourceAddress, 20), - sourcePubkey: dataToBytes(consolidationRequest.sourcePubkey, 48), - targetPubkey: dataToBytes(consolidationRequest.targetPubkey, 48), + deposits: deserializeDepositRequests(deposits), + withdrawals: deserializeWithdrawalRequest(withdrawals), + consolidations: deserializeConsolidationRequests(consolidations), }; } @@ -490,11 +449,6 @@ export function deserializeExecutionPayloadBody(data: ExecutionPayloadBodyRpc | ? { transactions: data.transactions.map((tran) => dataToBytes(tran, null)), withdrawals: data.withdrawals ? data.withdrawals.map(deserializeWithdrawal) : null, - depositRequests: data.depositRequests ? data.depositRequests.map(deserializeDepositRequest) : null, - withdrawalRequests: data.withdrawalRequests ? data.withdrawalRequests.map(deserializeWithdrawalRequest) : null, - consolidationRequests: data.consolidationRequests - ? data.consolidationRequests.map(deserializeConsolidationRequest) - : null, } : null; } @@ -504,11 +458,6 @@ export function serializeExecutionPayloadBody(data: ExecutionPayloadBody | null) ? { transactions: data.transactions.map((tran) => bytesToData(tran)), withdrawals: data.withdrawals ? data.withdrawals.map(serializeWithdrawal) : null, - depositRequests: data.depositRequests ? data.depositRequests.map(serializeDepositRequest) : null, - withdrawalRequests: data.withdrawalRequests ? data.withdrawalRequests.map(serializeWithdrawalRequest) : null, - consolidationRequests: data.consolidationRequests - ? data.consolidationRequests.map(serializeConsolidationRequest) - : null, } : null; } diff --git a/packages/beacon-node/src/execution/engine/utils.ts b/packages/beacon-node/src/execution/engine/utils.ts index b56f62bf602b..4d84eda52c44 100644 --- a/packages/beacon-node/src/execution/engine/utils.ts +++ b/packages/beacon-node/src/execution/engine/utils.ts @@ -12,7 +12,7 @@ import {isQueueErrorAborted} from "../../util/queue/errors.js"; import {ExecutionPayloadStatus, ExecutionEngineState} from "./interface.js"; export type JsonRpcBackend = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: readonly handlers: Record any>; }; @@ -27,8 +27,7 @@ export class ExecutionEngineMockJsonRpcClient implements IJsonRpcHttpClient { if (handler === undefined) { throw Error(`Unknown method ${payload.method}`); } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: return handler(...(payload.params as any[])) as R; }, payload); } diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index 96bb6bea4174..b9a02a3b2059 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -3,6 +3,11 @@ import {NotReorgedReason} from "@lodestar/fork-choice/lib/forkChoice/interface.j import {UpdateHeadOpt} from "@lodestar/fork-choice"; import {RegistryMetricCreator} from "../utils/registryMetricCreator.js"; import {BlockProductionStep, PayloadPreparationType} from "../../chain/produceBlock/index.js"; +import { + BlockSelectionResult, + BuilderBlockSelectionReason, + EngineBlockSelectionReason, +} from "../../api/impl/validator/index.js"; export type BeaconMetrics = ReturnType; @@ -11,7 +16,6 @@ export type BeaconMetrics = ReturnType; * https://github.com/ethereum/beacon-metrics/ and * https://hackmd.io/D5FmoeFZScim_squBFl8oA */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function createBeaconMetrics(register: RegistryMetricCreator) { return { // From https://github.com/ethereum/beacon-metrics/blob/master/metrics.md @@ -59,11 +63,11 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { // Non-spec'ed forkChoice: { - findHead: register.histogram<{entrypoint: string}>({ + findHead: register.histogram<{caller: string}>({ name: "beacon_fork_choice_find_head_seconds", help: "Time taken to find head in seconds", buckets: [0.1, 1, 10], - labelNames: ["entrypoint"], + labelNames: ["caller"], }), requests: register.gauge({ name: "beacon_fork_choice_requests_total", @@ -95,7 +99,7 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { }), queuedAttestations: register.gauge({ name: "beacon_fork_choice_queued_attestations_count", - help: "Current count of queued_attestations in fork choice data structures", + help: "Count of queued_attestations in fork choice per slot", }), validatedAttestationDatas: register.gauge({ name: "beacon_fork_choice_validated_attestation_datas_count", @@ -122,7 +126,7 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { headState: { unfinalizedPubkeyCacheSize: register.gauge({ - name: "head_state_unfinalized_pubkey_cache_size", + name: "beacon_head_state_unfinalized_pubkey_cache_size", help: "Current size of the unfinalizedPubkey2Index cache in the head state", }), }, @@ -161,12 +165,23 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { help: "Count of blocks successfully produced", labelNames: ["source"], }), + blockProductionSelectionResults: register.gauge({ + name: "beacon_block_production_selection_results_total", + help: "Count of all block production selection results", + labelNames: ["source", "reason"], + }), blockProductionNumAggregated: register.histogram<{source: ProducedBlockSource}>({ name: "beacon_block_production_num_aggregated_total", help: "Count of all aggregated attestations in our produced block", buckets: [32, 64, 96, 128], labelNames: ["source"], }), + blockProductionExecutionPayloadValue: register.histogram<{source: ProducedBlockSource}>({ + name: "beacon_block_production_execution_payload_value", + help: "Execution payload value denominated in ETH of produced blocks", + buckets: [0.001, 0.005, 0.01, 0.03, 0.05, 0.07, 0.1, 0.3, 0.5, 1], + labelNames: ["source"], + }), blockProductionCaches: { producedBlockRoot: register.gauge({ diff --git a/packages/beacon-node/src/metrics/metrics/lodestar.ts b/packages/beacon-node/src/metrics/metrics/lodestar.ts index 737a900e5f64..ac2cca319775 100644 --- a/packages/beacon-node/src/metrics/metrics/lodestar.ts +++ b/packages/beacon-node/src/metrics/metrics/lodestar.ts @@ -25,7 +25,6 @@ export type LodestarMetrics = ReturnType; /** * Extra Lodestar custom metrics */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function createLodestarMetrics( register: RegistryMetricCreator, metadata?: LodestarMetadata, @@ -381,11 +380,11 @@ export function createLodestarMetrics( epochCache: { finalizedPubkeyDuplicateInsert: register.gauge({ - name: "lodestar_epoch_cache_finalized_pubkey_duplicate_insert", + name: "lodestar_epoch_cache_finalized_pubkey_duplicate_insert_total", help: "Total count of duplicate insert of finalized pubkeys", }), newUnFinalizedPubkey: register.gauge({ - name: "lodestar_epoch_cache_new_unfinalized_pubkey", + name: "lodestar_epoch_cache_new_unfinalized_pubkey_total", help: "Total count of unfinalized pubkeys added", }), }, @@ -498,11 +497,6 @@ export function createLodestarMetrics( name: "lodestar_bls_thread_pool_batchable_sig_sets_total", help: "Count of total batchable signature sets", }), - aggregateWithRandomnessMainThreadDuration: register.histogram({ - name: "lodestar_bls_thread_pool_aggregate_with_randomness_main_thread_time_seconds", - help: "Total time performing aggregateWithRandomness on main thread", - buckets: [0.001, 0.005, 0.01, 0.1], - }), pubkeysAggregationMainThreadDuration: register.histogram({ name: "lodestar_bls_thread_pool_pubkeys_aggregation_main_thread_time_seconds", help: "Total time spent aggregating pubkeys on main thread", @@ -1218,11 +1212,6 @@ export function createLodestarMetrics( help: "Histogram of cloned count per state every time state.clone() is called", buckets: [1, 2, 5, 10, 50, 250], }), - stateSerializeDuration: register.histogram({ - name: "lodestar_cp_state_cache_state_serialize_seconds", - help: "Histogram of time to serialize state to db", - buckets: [0.1, 0.5, 1, 2, 3, 4], - }), numStatesUpdated: register.histogram({ name: "lodestar_cp_state_cache_state_updated_count", help: "Histogram of number of state cache items updated every time removing and adding pubkeys to pubkey cache", @@ -1297,22 +1286,45 @@ export function createLodestarMetrics( name: "lodestar_shuffling_cache_size", help: "Shuffling cache size", }), - processStateInsertNew: register.gauge({ - name: "lodestar_shuffling_cache_process_state_insert_new_total", - help: "Total number of times processState is called resulting a new shuffling", - }), - processStateUpdatePromise: register.gauge({ - name: "lodestar_shuffling_cache_process_state_update_promise_total", - help: "Total number of times processState is called resulting a promise being updated with shuffling", - }), - processStateNoOp: register.gauge({ - name: "lodestar_shuffling_cache_process_state_no_op_total", - help: "Total number of times processState is called resulting no changes", - }), insertPromiseCount: register.gauge({ name: "lodestar_shuffling_cache_insert_promise_count", help: "Total number of times insertPromise is called", }), + hit: register.gauge({ + name: "lodestar_shuffling_cache_hit_count", + help: "Count of shuffling cache hit", + }), + miss: register.gauge({ + name: "lodestar_shuffling_cache_miss_count", + help: "Count of shuffling cache miss", + }), + shufflingBuiltMultipleTimes: register.gauge({ + name: "lodestar_shuffling_cache_recalculated_shuffling_count", + help: "Count of shuffling that were build multiple times", + }), + shufflingPromiseNotResolvedAndThrownAway: register.gauge({ + name: "lodestar_shuffling_cache_promise_not_resolved_and_thrown_away_count", + help: "Count of shuffling cache promises that were discarded and the shuffling was built synchronously", + }), + shufflingPromiseNotResolved: register.gauge({ + name: "lodestar_shuffling_cache_promise_not_resolved_count", + help: "Count of shuffling cache promises that were requested before the promise was resolved", + }), + nextShufflingNotOnEpochCache: register.gauge({ + name: "lodestar_shuffling_cache_next_shuffling_not_on_epoch_cache", + help: "The next shuffling was not on the epoch cache before the epoch transition", + }), + shufflingPromiseResolutionTime: register.histogram({ + name: "lodestar_shuffling_cache_promise_resolution_time_seconds", + help: "Time from promise insertion until promise resolution when shuffling was ready in seconds", + buckets: [0.5, 1, 1.5, 2], + }), + shufflingCalculationTime: register.histogram<{source: "build" | "getSync"}>({ + name: "lodestar_shuffling_cache_shuffling_calculation_time_seconds", + help: "Run time of shuffling calculation", + buckets: [0.5, 0.75, 1, 1.25, 1.5], + labelNames: ["source"], + }), }, seenCache: { @@ -1411,6 +1423,12 @@ export function createLodestarMetrics( name: "lodestar_unhandled_promise_rejections_total", help: "UnhandledPromiseRejection total count", }), + stateSerializeDuration: register.histogram<{source: AllocSource}>({ + name: "lodestar_state_serialize_seconds", + help: "Histogram of time to serialize state", + labelNames: ["source"], + buckets: [0.1, 0.5, 1, 2, 3, 4], + }), // regen.getState metrics regenGetState: { diff --git a/packages/beacon-node/src/metrics/validatorMonitor.ts b/packages/beacon-node/src/metrics/validatorMonitor.ts index 34dfa6b72e03..14a210f62997 100644 --- a/packages/beacon-node/src/metrics/validatorMonitor.ts +++ b/packages/beacon-node/src/metrics/validatorMonitor.ts @@ -814,7 +814,7 @@ function renderAttestationSummary( } // - else if (flags.timelyTarget) { + if (flags.timelyTarget) { // timelyHead == false, means at least one is true // - attestation voted incorrect head // - attestation was included late @@ -870,57 +870,55 @@ function renderAttestationSummary( } // - else if (flags.timelySource) { + if (flags.timelySource) { // timelyTarget == false && timelySource == true means that // - attestation voted the wrong target but distance is <= integer_squareroot(SLOTS_PER_EPOCH) return "wrong_target_timely_source"; } // - else { - // timelySource == false, either: - // - attestation was not included in the block - // - included in block with wrong target (very unlikely) - // - included in block with distance > SLOTS_PER_EPOCH (very unlikely) - - // Validator failed to submit an attestation for this epoch, validator client is probably offline - if (!summary || summary.poolSubmitDelayMinSec === null) { - return "no_submission"; - } + // timelySource == false, either: + // - attestation was not included in the block + // - included in block with wrong target (very unlikely) + // - included in block with distance > SLOTS_PER_EPOCH (very unlikely) + + // Validator failed to submit an attestation for this epoch, validator client is probably offline + if (!summary || summary.poolSubmitDelayMinSec === null) { + return "no_submission"; + } - const canonicalBlockInclusion = summary.blockInclusions.find((block) => isCanonical(rootCache, block)); - if (canonicalBlockInclusion) { - // Canonical block inclusion with no participation flags set means wrong target + late source - return "wrong_target_late_source"; - } + const canonicalBlockInclusion = summary.blockInclusions.find((block) => isCanonical(rootCache, block)); + if (canonicalBlockInclusion) { + // Canonical block inclusion with no participation flags set means wrong target + late source + return "wrong_target_late_source"; + } - const submittedLate = - summary.poolSubmitDelayMinSec > - (INTERVALS_LATE_ATTESTATION_SUBMISSION * config.SECONDS_PER_SLOT) / INTERVALS_PER_SLOT; - - const aggregateInclusion = summary.aggregateInclusionDelaysSec.length > 0; - - if (submittedLate && aggregateInclusion) { - return "late_submit"; - } else if (submittedLate && !aggregateInclusion) { - return "late_submit_no_aggregate_inclusion"; - } else if (!submittedLate && aggregateInclusion) { - // TODO: Why was it missed then? - if (summary.blockInclusions.length) { - return "block_inclusion_but_orphan"; - } else { - return "aggregate_inclusion_but_missed"; - } - // } else if (!submittedLate && !aggregateInclusion) { - } else { - // Did the node had enough peers? - if (summary.poolSubmitSentPeers === 0) { - return "sent_to_zero_peers"; - } else { - return "no_aggregate_inclusion"; - } + const submittedLate = + summary.poolSubmitDelayMinSec > + (INTERVALS_LATE_ATTESTATION_SUBMISSION * config.SECONDS_PER_SLOT) / INTERVALS_PER_SLOT; + + const aggregateInclusion = summary.aggregateInclusionDelaysSec.length > 0; + + if (submittedLate && aggregateInclusion) { + return "late_submit"; + } + if (submittedLate && !aggregateInclusion) { + return "late_submit_no_aggregate_inclusion"; + } + + if (!submittedLate && aggregateInclusion) { + // TODO: Why was it missed then? + if (summary.blockInclusions.length) { + return "block_inclusion_but_orphan"; } + return "aggregate_inclusion_but_missed"; + // } else if (!submittedLate && !aggregateInclusion) { } + // Did the node had enough peers? + if (summary.poolSubmitSentPeers === 0) { + return "sent_to_zero_peers"; + } + return "no_aggregate_inclusion"; } function whyIsHeadVoteWrong(rootCache: RootHexCache, canonicalBlockInclusion: AttestationBlockInclusion): string { @@ -968,9 +966,7 @@ function whyIsHeadVoteWrong(rootCache: RootHexCache, canonicalBlockInclusion: At // \_(A)_(A) // // Vote for different heads on skipped slot - else { - return "wrong_head_vote"; - } + return "wrong_head_vote"; } function whyIsDistanceNotOk( @@ -989,9 +985,7 @@ function whyIsDistanceNotOk( } // - else { - return "late_unknown"; - } + return "late_unknown"; } /** Returns true if the state's root record includes `block` */ diff --git a/packages/beacon-node/src/monitoring/properties.ts b/packages/beacon-node/src/monitoring/properties.ts index 55b94dd159b7..2ab819179863 100644 --- a/packages/beacon-node/src/monitoring/properties.ts +++ b/packages/beacon-node/src/monitoring/properties.ts @@ -140,11 +140,11 @@ export class MetricProperty implements ClientStatsPropert case JsonType.Boolean: if (this.definition.rangeValue != null) { return value === this.definition.rangeValue; - } else if (this.definition.threshold != null) { + } + if (this.definition.threshold != null) { return value >= this.definition.threshold; - } else { - return value > 0; } + return value > 0; } } return value; diff --git a/packages/beacon-node/src/monitoring/service.ts b/packages/beacon-node/src/monitoring/service.ts index 9581c5f11c92..f6a6a2352e02 100644 --- a/packages/beacon-node/src/monitoring/service.ts +++ b/packages/beacon-node/src/monitoring/service.ts @@ -191,11 +191,11 @@ export class MonitoringService { // error was thrown by abort signal if (signal.reason === FetchAbortReason.Close) { throw new ErrorAborted("request"); - } else if (signal.reason === FetchAbortReason.Timeout) { + } + if (signal.reason === FetchAbortReason.Timeout) { throw new TimeoutError("request"); - } else { - throw e; } + throw e; } finally { timer({status: res?.ok ? SendDataStatus.Success : SendDataStatus.Error}); clearTimeout(timeout); @@ -229,7 +229,7 @@ export class MonitoringService { } return url; - } catch { + } catch (_e) { throw new Error(`Monitoring endpoint must be a valid URL: ${endpoint}`); } } diff --git a/packages/beacon-node/src/monitoring/system.ts b/packages/beacon-node/src/monitoring/system.ts index a1a79b1bd277..83507443be01 100644 --- a/packages/beacon-node/src/monitoring/system.ts +++ b/packages/beacon-node/src/monitoring/system.ts @@ -3,7 +3,6 @@ import os from "node:os"; import path from "node:path"; // We want to keep `system` export as it's more readable and easier to understand -// eslint-disable-next-line import/no-named-as-default import system from "systeminformation"; import {Logger} from "@lodestar/utils"; diff --git a/packages/beacon-node/src/network/core/metrics.ts b/packages/beacon-node/src/network/core/metrics.ts index c267ec069d88..7d0ef47d84af 100644 --- a/packages/beacon-node/src/network/core/metrics.ts +++ b/packages/beacon-node/src/network/core/metrics.ts @@ -5,7 +5,6 @@ import {SubnetSource} from "../subnets/attnetsService.js"; export type NetworkCoreMetrics = ReturnType; -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function createNetworkCoreMetrics(register: RegistryMetricCreator) { return { register, @@ -254,7 +253,6 @@ export function createNetworkCoreMetrics(register: RegistryMetricCreator) { export type NetworkCoreWorkerMetrics = ReturnType; -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function getNetworkCoreWorkerMetrics(register: RegistryMetricCreator) { return { reqRespBridgeRespCallerPending: register.gauge({ diff --git a/packages/beacon-node/src/network/core/networkCore.ts b/packages/beacon-node/src/network/core/networkCore.ts index 07b346bc29e4..d47fcabd4146 100644 --- a/packages/beacon-node/src/network/core/networkCore.ts +++ b/packages/beacon-node/src/network/core/networkCore.ts @@ -2,13 +2,12 @@ import {Connection, PeerId} from "@libp2p/interface"; import {multiaddr} from "@multiformats/multiaddr"; import {PublishOpts} from "@chainsafe/libp2p-gossipsub/types"; import {PeerScoreStatsDump} from "@chainsafe/libp2p-gossipsub/dist/src/score/peer-score.js"; -import {fromHexString} from "@chainsafe/ssz"; import {ENR} from "@chainsafe/enr"; import {routes} from "@lodestar/api"; import {BeaconConfig} from "@lodestar/config"; import type {LoggerNode} from "@lodestar/logger/node"; import {Epoch, phase0} from "@lodestar/types"; -import {withTimeout} from "@lodestar/utils"; +import {fromHex, withTimeout} from "@lodestar/utils"; import {ForkName} from "@lodestar/params"; import {ResponseIncoming} from "@lodestar/reqresp"; import {Libp2p} from "../interface.js"; @@ -150,7 +149,7 @@ export class NetworkCore implements INetworkCore { // Bind discv5's ENR to local metadata // resolve circular dependency by setting `discv5` variable after the peer manager is instantiated - // eslint-disable-next-line prefer-const + // biome-ignore lint/style/useConst: let discv5: Discv5Worker | undefined; const onMetadataSetValue = function onMetadataSetValue(key: string, value: Uint8Array): void { discv5?.setEnrValue(key, value).catch((e) => logger.error("error on setEnrValue", {key}, e)); @@ -195,7 +194,7 @@ export class NetworkCore implements INetworkCore { await gossip.start(); const enr = opts.discv5?.enr; - const nodeId = enr ? fromHexString(ENR.decodeTxt(enr).nodeId) : null; + const nodeId = enr ? fromHex(ENR.decodeTxt(enr).nodeId) : null; const attnetsService = new AttnetsService(config, clock, gossip, metadata, logger, metrics, nodeId, opts); const syncnetsService = new SyncnetsService(config, clock, gossip, metadata, logger, metrics, opts); @@ -224,6 +223,7 @@ export class NetworkCore implements INetworkCore { reqResp.registerProtocolsAtFork(forkCurrentSlot); // Bind discv5's ENR to local metadata + // biome-ignore lint/complexity/useLiteralKeys: `discovery` is a private attribute discv5 = peerManager["discovery"]?.discv5; // Initialize ENR with clock's fork @@ -277,6 +277,7 @@ export class NetworkCore implements INetworkCore { async scrapeMetrics(): Promise { return [ (await this.metrics?.register.metrics()) ?? "", + // biome-ignore lint/complexity/useLiteralKeys: `discovery` is a private attribute (await this.peerManager["discovery"]?.discv5.scrapeMetrics()) ?? "", ] .filter((str) => str.length > 0) @@ -344,6 +345,7 @@ export class NetworkCore implements INetworkCore { // REST API queries async getNetworkIdentity(): Promise { + // biome-ignore lint/complexity/useLiteralKeys: `discovery` is a private attribute const enr = await this.peerManager["discovery"]?.discv5.enr(); const discoveryAddresses = [ enr?.getLocationMultiaddr("tcp")?.toString() ?? null, @@ -406,6 +408,7 @@ export class NetworkCore implements INetworkCore { } async dumpDiscv5KadValues(): Promise { + // biome-ignore lint/complexity/useLiteralKeys: `discovery` is a private attribute return (await this.peerManager["discovery"]?.discv5?.kadValues())?.map((enr) => enr.encodeTxt()) ?? []; } @@ -422,6 +425,7 @@ export class NetworkCore implements INetworkCore { } async writeDiscv5Profile(durationMs: number, dirpath: string): Promise { + // biome-ignore lint/complexity/useLiteralKeys: `discovery` is a private attribute return this.peerManager["discovery"]?.discv5.writeProfile(durationMs, dirpath) ?? "no discv5"; } @@ -430,6 +434,7 @@ export class NetworkCore implements INetworkCore { } writeDiscv5HeapSnapshot(prefix: string, dirpath: string): Promise { + // biome-ignore lint/complexity/useLiteralKeys: `discovery` is a private attribute return this.peerManager["discovery"]?.discv5.writeHeapSnapshot(prefix, dirpath) ?? Promise.resolve("no discv5"); } diff --git a/packages/beacon-node/src/network/core/networkCoreWorker.ts b/packages/beacon-node/src/network/core/networkCoreWorker.ts index 1df348335582..5e4b057402d8 100644 --- a/packages/beacon-node/src/network/core/networkCoreWorker.ts +++ b/packages/beacon-node/src/network/core/networkCoreWorker.ts @@ -28,7 +28,6 @@ import { // Cloned data from instantiation const workerData = worker.workerData as NetworkWorkerData; const parentPort = worker.parentPort; -// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!workerData) throw Error("workerData must be defined"); if (!parentPort) throw Error("parentPort must be defined"); diff --git a/packages/beacon-node/src/network/core/networkCoreWorkerHandler.ts b/packages/beacon-node/src/network/core/networkCoreWorkerHandler.ts index 19cca27eaaac..bd66a21e726b 100644 --- a/packages/beacon-node/src/network/core/networkCoreWorkerHandler.ts +++ b/packages/beacon-node/src/network/core/networkCoreWorkerHandler.ts @@ -136,7 +136,7 @@ export class WorkerNetworkCore implements INetworkCore { resourceLimits: {maxYoungGenerationSizeMb: opts.maxYoungGenerationSizeMb}, } as ConstructorParameters[1]); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: const networkThreadApi = (await spawn(worker, { // A Lodestar Node may do very expensive task at start blocking the event loop and causing // the initialization to timeout. The number below is big enough to almost disable the timeout diff --git a/packages/beacon-node/src/network/core/types.ts b/packages/beacon-node/src/network/core/types.ts index 41b7f669eaac..4eeaf96e1903 100644 --- a/packages/beacon-node/src/network/core/types.ts +++ b/packages/beacon-node/src/network/core/types.ts @@ -90,7 +90,7 @@ export type NetworkWorkerData = { */ export type NetworkWorkerApi = INetworkCorePublic & { // To satisfy the constraint of `ModuleThread` type - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: [string: string]: (...args: any[]) => Promise | any; // Async method through worker boundary reportPeer(peer: PeerIdStr, action: PeerAction, actionName: string): Promise; diff --git a/packages/beacon-node/src/network/discv5/index.ts b/packages/beacon-node/src/network/discv5/index.ts index b2136658dfe7..745b3171c38d 100644 --- a/packages/beacon-node/src/network/discv5/index.ts +++ b/packages/beacon-node/src/network/discv5/index.ts @@ -1,4 +1,4 @@ -import EventEmitter from "events"; +import EventEmitter from "node:events"; import {PeerId, Secp256k1PeerId} from "@libp2p/interface"; import {StrictEventEmitter} from "strict-event-emitter-types"; import {exportToProtobuf} from "@libp2p/peer-id-factory"; diff --git a/packages/beacon-node/src/network/discv5/utils.ts b/packages/beacon-node/src/network/discv5/utils.ts index e5707b483281..699675dfbe38 100644 --- a/packages/beacon-node/src/network/discv5/utils.ts +++ b/packages/beacon-node/src/network/discv5/utils.ts @@ -5,6 +5,7 @@ import {ENRKey} from "../metadata.js"; export enum ENRRelevance { no_tcp = "no_tcp", no_eth2 = "no_eth2", + // biome-ignore lint/style/useNamingConvention: Need to use the this name for network convention unknown_forkDigest = "unknown_forkDigest", relevant = "relevant", } diff --git a/packages/beacon-node/src/network/discv5/worker.ts b/packages/beacon-node/src/network/discv5/worker.ts index be8fdc4af601..8e96751d5fe7 100644 --- a/packages/beacon-node/src/network/discv5/worker.ts +++ b/packages/beacon-node/src/network/discv5/worker.ts @@ -22,7 +22,6 @@ import {enrRelevance, ENRRelevance} from "./utils.js"; // Cloned data from instatiation const workerData = worker.workerData as Discv5WorkerData; -// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!workerData) throw Error("workerData must be defined"); const logger = getNodeLogger(workerData.loggerOpts); diff --git a/packages/beacon-node/src/network/events.ts b/packages/beacon-node/src/network/events.ts index 45759de61073..a95b52394163 100644 --- a/packages/beacon-node/src/network/events.ts +++ b/packages/beacon-node/src/network/events.ts @@ -1,4 +1,4 @@ -import {EventEmitter} from "events"; +import {EventEmitter} from "node:events"; import {PeerId, TopicValidatorResult} from "@libp2p/interface"; import {phase0, RootHex} from "@lodestar/types"; import {BlockInput, NullBlockInput} from "../chain/blocks/types.js"; diff --git a/packages/beacon-node/src/network/forks.ts b/packages/beacon-node/src/network/forks.ts index 47575cf6d9c6..1c613f4cee94 100644 --- a/packages/beacon-node/src/network/forks.ts +++ b/packages/beacon-node/src/network/forks.ts @@ -86,7 +86,6 @@ export function getCurrentAndNextFork( } return { - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions currentFork: forks[currentForkIdx] || forks[0], nextFork: hasNextFork ? forks[nextForkIdx] : undefined, }; diff --git a/packages/beacon-node/src/network/gossip/encoding.ts b/packages/beacon-node/src/network/gossip/encoding.ts index 02c0df07b2f1..f7f733fcd915 100644 --- a/packages/beacon-node/src/network/gossip/encoding.ts +++ b/packages/beacon-node/src/network/gossip/encoding.ts @@ -25,9 +25,8 @@ const sharedMsgIdBuf = Buffer.alloc(20); export function fastMsgIdFn(rpcMsg: RPC.Message): string { if (rpcMsg.data) { return xxhash.h64Raw(rpcMsg.data, h64Seed).toString(16); - } else { - return "0000000000000000"; } + return "0000000000000000"; } export function msgIdToStrFn(msgId: Uint8Array): string { diff --git a/packages/beacon-node/src/network/gossip/gossipsub.ts b/packages/beacon-node/src/network/gossip/gossipsub.ts index e6977abe8ce6..76e1330cd4a1 100644 --- a/packages/beacon-node/src/network/gossip/gossipsub.ts +++ b/packages/beacon-node/src/network/gossip/gossipsub.ts @@ -26,7 +26,6 @@ import { GOSSIP_D_LOW, } from "./scoringParameters.js"; -/* eslint-disable @typescript-eslint/naming-convention */ /** As specified in https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/p2p-interface.md */ const GOSSIPSUB_HEARTBEAT_INTERVAL = 0.7 * 1000; @@ -122,7 +121,7 @@ export class Eth2Gossipsub extends GossipSub { // TODO: figure out a way to dynamically transition to the size dataTransform: new DataTransformSnappy( gossipTopicCache, - isFinite(config.BELLATRIX_FORK_EPOCH) ? GOSSIP_MAX_SIZE_BELLATRIX : GOSSIP_MAX_SIZE + Number.isFinite(config.BELLATRIX_FORK_EPOCH) ? GOSSIP_MAX_SIZE_BELLATRIX : GOSSIP_MAX_SIZE ), metricsRegister: metricsRegister as MetricsRegister | null, metricsTopicStrToLabel: metricsRegister @@ -181,10 +180,11 @@ export class Eth2Gossipsub extends GossipSub { } private onScrapeLodestarMetrics(metrics: Eth2GossipsubMetrics): void { - const mesh = this["mesh"]; + const mesh = this.mesh; + // biome-ignore lint/complexity/useLiteralKeys: `topics` is a private attribute const topics = this["topics"] as Map>; - const peers = this["peers"]; - const score = this["score"]; + const peers = this.peers; + const score = this.score; const meshPeersByClient = new Map(); const meshPeerIdStrs = new Set(); @@ -321,7 +321,8 @@ export class Eth2Gossipsub extends GossipSub { */ function attSubnetLabel(subnet: number): string { if (subnet > 9) return String(subnet); - else return `0${subnet}`; + + return `0${subnet}`; } function getMetricsTopicStrToLabel(config: BeaconConfig, opts: {disableLightClientServer: boolean}): TopicStrToLabel { diff --git a/packages/beacon-node/src/network/gossip/interface.ts b/packages/beacon-node/src/network/gossip/interface.ts index ab9b8a65978d..ff1bfa088c17 100644 --- a/packages/beacon-node/src/network/gossip/interface.ts +++ b/packages/beacon-node/src/network/gossip/interface.ts @@ -36,6 +36,9 @@ export enum GossipType { bls_to_execution_change = "bls_to_execution_change", } +export type SequentialGossipType = Exclude; +export type BatchGossipType = GossipType.beacon_attestation; + export enum GossipEncoding { ssz_snappy = "ssz_snappy", } @@ -181,26 +184,26 @@ export type GossipHandlerParamGeneric = { }; export type GossipHandlers = { - [K in GossipType]: DefaultGossipHandler | BatchGossipHandler; + [K in GossipType]: SequentialGossipHandler | BatchGossipHandler; }; -export type DefaultGossipHandler = ( +export type SequentialGossipHandler = ( gossipHandlerParam: GossipHandlerParamGeneric ) => Promise; -export type DefaultGossipHandlers = { - [K in GossipType]: DefaultGossipHandler; +export type SequentialGossipHandlers = { + [K in SequentialGossipType]: SequentialGossipHandler; +}; + +export type BatchGossipHandlers = { + [K in BatchGossipType]: BatchGossipHandler; }; export type BatchGossipHandler = ( gossipHandlerParams: GossipHandlerParamGeneric[] ) => Promise<(null | GossipActionError)[]>; -export type BatchGossipHandlers = { - [K in GossipType]?: BatchGossipHandler; -}; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export type ResolvedType Promise> = F extends (...args: any) => Promise ? T : never; diff --git a/packages/beacon-node/src/network/gossip/metrics.ts b/packages/beacon-node/src/network/gossip/metrics.ts index c2b5d0b32338..5ca5d22154c2 100644 --- a/packages/beacon-node/src/network/gossip/metrics.ts +++ b/packages/beacon-node/src/network/gossip/metrics.ts @@ -4,7 +4,6 @@ import {GossipType} from "./interface.js"; export type Eth2GossipsubMetrics = ReturnType; -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function createEth2GossipsubMetrics(register: RegistryMetricCreator) { return { gossipPeer: { diff --git a/packages/beacon-node/src/network/gossip/scoringParameters.ts b/packages/beacon-node/src/network/gossip/scoringParameters.ts index 7e1be07f2ca7..3ba32614afeb 100644 --- a/packages/beacon-node/src/network/gossip/scoringParameters.ts +++ b/packages/beacon-node/src/network/gossip/scoringParameters.ts @@ -12,8 +12,6 @@ import {Eth2Context, Eth2GossipsubModules} from "./gossipsub.js"; import {GossipType} from "./interface.js"; import {stringifyGossipTopic} from "./topic.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - export const GOSSIP_D = 8; export const GOSSIP_D_LOW = 6; export const GOSSIP_D_HIGH = 12; diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index b7c7425584c5..ed44c8314425 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -78,7 +78,6 @@ function stringifyGossipTopicType(topic: GossipTopic): string { } } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function getGossipSSZType(topic: GossipTopic) { switch (topic.type) { case GossipType.beacon_block: @@ -120,7 +119,7 @@ export function sszDeserialize(topic: T, serializedData: const sszType = getGossipSSZType(topic); try { return sszType.deserialize(serializedData) as SSZTypeOfGossipTopic; - } catch (e) { + } catch (_e) { throw new GossipActionError(GossipAction.REJECT, {code: GossipErrorCode.INVALID_SERIALIZED_BYTES_ERROR_CODE}); } } @@ -131,14 +130,14 @@ export function sszDeserialize(topic: T, serializedData: export function sszDeserializeAttestation(fork: ForkName, serializedData: Uint8Array): Attestation { try { return sszTypesFor(fork).Attestation.deserialize(serializedData); - } catch (e) { + } catch (_e) { throw new GossipActionError(GossipAction.REJECT, {code: GossipErrorCode.INVALID_SERIALIZED_BYTES_ERROR_CODE}); } } // Parsing -const gossipTopicRegex = new RegExp("^/eth2/(\\w+)/(\\w+)/(\\w+)"); +const gossipTopicRegex = /^\/eth2\/(\w+)\/(\w+)\/(\w+)/; /** * Parse a `GossipTopic` object from its stringified form. diff --git a/packages/beacon-node/src/network/interface.ts b/packages/beacon-node/src/network/interface.ts index 8d73379af221..0d48df42b31e 100644 --- a/packages/beacon-node/src/network/interface.ts +++ b/packages/beacon-node/src/network/interface.ts @@ -27,6 +27,7 @@ import { deneb, phase0, SignedAggregateAndProof, + WithBytes, } from "@lodestar/types"; import {PeerIdStr} from "../util/peerId.js"; import {INetworkEventBus} from "./events.js"; @@ -35,8 +36,6 @@ import {GossipType} from "./gossip/interface.js"; import {PendingGossipsubMessage} from "./processor/types.js"; import {PeerAction} from "./peers/index.js"; -export type WithBytes = {data: T; bytes: Uint8Array}; - /** * The architecture of the network looks like so: * - core: diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index 1b3ccaaaf75a..15414fcf9138 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -18,6 +18,7 @@ import { LightClientOptimisticUpdate, LightClientUpdate, SignedAggregateAndProof, + WithBytes, } from "@lodestar/types"; import {routes} from "@lodestar/api"; import {ResponseIncoming} from "@lodestar/reqresp"; @@ -28,7 +29,7 @@ import {IBeaconDb} from "../db/interface.js"; import {PeerIdStr, peerIdToString} from "../util/peerId.js"; import {IClock} from "../util/clock.js"; import {NetworkOptions} from "./options.js"; -import {WithBytes, INetwork} from "./interface.js"; +import {INetwork} from "./interface.js"; import {ReqRespMethod} from "./reqresp/index.js"; import {GossipHandlers, GossipTopicMap, GossipType, GossipTypeMap} from "./gossip/index.js"; import {PeerAction, PeerScoreStats} from "./peers/index.js"; diff --git a/packages/beacon-node/src/network/options.ts b/packages/beacon-node/src/network/options.ts index d2070873261b..ebb321584d12 100644 --- a/packages/beacon-node/src/network/options.ts +++ b/packages/beacon-node/src/network/options.ts @@ -40,8 +40,6 @@ export const defaultNetworkOptions: NetworkOptions = { maxYoungGenerationSizeMb: 152, // subscribe 2 slots before aggregator dutied slot to get stable mesh peers as monitored on goerli slotsToSubscribeBeforeAggregatorDuty: 2, - // this should only be set to true if useWorker is true - beaconAttestationBatchValidation: true, // This will enable the light client server by default disableLightClientServer: false, }; diff --git a/packages/beacon-node/src/network/peers/datastore.ts b/packages/beacon-node/src/network/peers/datastore.ts index 762e99dc45b4..88a7a6f5f2d6 100644 --- a/packages/beacon-node/src/network/peers/datastore.ts +++ b/packages/beacon-node/src/network/peers/datastore.ts @@ -147,8 +147,7 @@ export class Eth2PeerDataStore extends BaseDatastore { if (this._dirtyItems.size >= this._threshold) { try { await this._commitData(); - // eslint-disable-next-line no-empty - } catch (e) {} + } catch (_e) {} } } diff --git a/packages/beacon-node/src/network/peers/discover.ts b/packages/beacon-node/src/network/peers/discover.ts index 79603f780e3d..2b03656064e4 100644 --- a/packages/beacon-node/src/network/peers/discover.ts +++ b/packages/beacon-node/src/network/peers/discover.ts @@ -277,9 +277,8 @@ export class PeerDiscovery { if (this.randomNodeQuery.code === QueryStatusCode.Active) { this.metrics?.discovery.findNodeQueryRequests.inc({action: "ignore"}); return; - } else { - this.metrics?.discovery.findNodeQueryRequests.inc({action: "start"}); } + this.metrics?.discovery.findNodeQueryRequests.inc({action: "start"}); // Use async version to prevent blocking the event loop // Time to completion of this function is not critical, in case this async call add extra lag @@ -304,7 +303,6 @@ export class PeerDiscovery { const {id, multiaddrs} = evt.detail; // libp2p may send us PeerInfos without multiaddrs https://github.com/libp2p/js-libp2p/issues/1873 - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!multiaddrs || multiaddrs.length === 0) { this.metrics?.discovery.discoveredStatus.inc({status: DiscoveredPeerStatus.no_multiaddrs}); return; @@ -373,7 +371,7 @@ export class PeerDiscovery { if ( this.libp2p.services.components.connectionManager .getDialQueue() - .find((pendingDial) => pendingDial.peerId && pendingDial.peerId.equals(peerId)) + .find((pendingDial) => pendingDial.peerId?.equals(peerId)) ) { return DiscoveredPeerStatus.already_dialing; } @@ -390,13 +388,13 @@ export class PeerDiscovery { if (this.shouldDialPeer(cachedPeer)) { void this.dialPeer(cachedPeer); return DiscoveredPeerStatus.attempt_dial; - } else { - // Add to pending good peers with a last seen time - this.cachedENRs.set(peerId.toString(), cachedPeer); - const dropped = pruneSetToMax(this.cachedENRs, MAX_CACHED_ENRS); - // If the cache was already full, count the peer as dropped - return dropped > 0 ? DiscoveredPeerStatus.dropped : DiscoveredPeerStatus.cached; } + + // Add to pending good peers with a last seen time + this.cachedENRs.set(peerId.toString(), cachedPeer); + const dropped = pruneSetToMax(this.cachedENRs, MAX_CACHED_ENRS); + // If the cache was already full, count the peer as dropped + return dropped > 0 ? DiscoveredPeerStatus.dropped : DiscoveredPeerStatus.cached; } catch (e) { this.logger.error("Error onDiscovered", {}, e as Error); return DiscoveredPeerStatus.error; @@ -474,7 +472,7 @@ export class PeerDiscovery { /** Check if there is 1+ open connection with this peer */ private isPeerConnected(peerIdStr: PeerIdStr): boolean { const connections = getConnectionsMap(this.libp2p).get(peerIdStr); - return Boolean(connections && connections.some((connection) => connection.status === "open")); + return Boolean(connections?.some((connection) => connection.status === "open")); } } diff --git a/packages/beacon-node/src/network/peers/peerManager.ts b/packages/beacon-node/src/network/peers/peerManager.ts index 43fbee723f0e..b8742789d4fb 100644 --- a/packages/beacon-node/src/network/peers/peerManager.ts +++ b/packages/beacon-node/src/network/peers/peerManager.ts @@ -276,11 +276,14 @@ export class PeerManager { switch (request.method) { case ReqRespMethod.Ping: - return this.onPing(peer, request.body); + this.onPing(peer, request.body); + return; case ReqRespMethod.Goodbye: - return this.onGoodbye(peer, request.body); + this.onGoodbye(peer, request.body); + return; case ReqRespMethod.Status: - return this.onStatus(peer, request.body); + this.onStatus(peer, request.body); + return; } } catch (e) { this.logger.error("Error onRequest handler", {}, e as Error); @@ -383,7 +386,7 @@ export class PeerManager { private async requestMetadata(peer: PeerId): Promise { try { this.onMetadata(peer, await this.reqResp.sendMetadata(peer)); - } catch (e) { + } catch (_e) { // TODO: Downvote peer here or in the reqResp layer } } @@ -395,7 +398,7 @@ export class PeerManager { // If peer replies a PING request also update lastReceivedMsg const peerData = this.connectedPeers.get(peer.toString()); if (peerData) peerData.lastReceivedMsgUnixTsMs = Date.now(); - } catch (e) { + } catch (_e) { // TODO: Downvote peer here or in the reqResp layer } } @@ -403,7 +406,7 @@ export class PeerManager { private async requestStatus(peer: PeerId, localStatus: phase0.Status): Promise { try { this.onStatus(peer, await this.reqResp.sendStatus(peer, localStatus)); - } catch (e) { + } catch (_e) { // TODO: Failed to get peer latest status: downvote but don't disconnect } } @@ -423,7 +426,7 @@ export class PeerManager { * NOTE: Discovery should only add a new query if one isn't already queued. */ private heartbeat(): void { - // timer is safe without a try {} catch {}, in case of error the metric won't register and timer is GC'ed + // timer is safe without a try {} catch (_e) {}, in case of error the metric won't register and timer is GC'ed const timer = this.metrics?.peerManager.heartbeatDuration.startTimer(); const connectedPeers = this.getConnectedPeerIds(); diff --git a/packages/beacon-node/src/network/peers/utils/assertPeerRelevance.ts b/packages/beacon-node/src/network/peers/utils/assertPeerRelevance.ts index 96e35e9d317d..e588b1ae0308 100644 --- a/packages/beacon-node/src/network/peers/utils/assertPeerRelevance.ts +++ b/packages/beacon-node/src/network/peers/utils/assertPeerRelevance.ts @@ -1,6 +1,5 @@ -import {toHexString} from "@chainsafe/ssz"; import {ForkDigest, Root, Slot, phase0, ssz} from "@lodestar/types"; -import {toRootHex} from "@lodestar/utils"; +import {toHex, toRootHex} from "@lodestar/utils"; // TODO: Why this value? (From Lighthouse) const FUTURE_SLOT_TOLERANCE = 1; @@ -79,7 +78,7 @@ export function isZeroRoot(root: Root): boolean { export function renderIrrelevantPeerType(type: IrrelevantPeerType): string { switch (type.code) { case IrrelevantPeerCode.INCOMPATIBLE_FORKS: - return `INCOMPATIBLE_FORKS ours: ${toHexString(type.ours)} theirs: ${toHexString(type.theirs)}`; + return `INCOMPATIBLE_FORKS ours: ${toHex(type.ours)} theirs: ${toHex(type.theirs)}`; case IrrelevantPeerCode.DIFFERENT_CLOCKS: return `DIFFERENT_CLOCKS slotDiff: ${type.slotDiff}`; case IrrelevantPeerCode.DIFFERENT_FINALIZED: diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 9e6f08a803c1..dab3af0df8ba 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -20,7 +20,7 @@ import { } from "../../chain/errors/index.js"; import { BatchGossipHandlers, - DefaultGossipHandlers, + SequentialGossipHandlers, GossipHandlerParamGeneric, GossipHandlers, GossipType, @@ -36,8 +36,6 @@ import { validateGossipBlsToExecutionChange, AggregateAndProofValidationResult, validateGossipAttestationsSameAttData, - validateGossipAttestation, - AttestationValidationResult, GossipAttestation, } from "../../chain/validation/index.js"; import {NetworkEvent, NetworkEventBus} from "../events.js"; @@ -64,8 +62,6 @@ import {AggregatorTracker} from "./aggregatorTracker.js"; export type GossipHandlerOpts = { /** By default pass gossip attestations to forkchoice */ dontSendGossipAttestationsToForkchoice?: boolean; - /** By default don't validate gossip attestations in batch */ - beaconAttestationBatchValidation?: boolean; }; export type ValidatorFnsModules = { @@ -96,20 +92,15 @@ const BLOCK_AVAILABILITY_CUTOFF_MS = 3_000; * - Ethereum Consensus gossipsub protocol strictly defined a single topic for message */ export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipHandlerOpts): GossipHandlers { - const defaultHandlers = getDefaultHandlers(modules, options); - if (options.beaconAttestationBatchValidation) { - const batchHandlers = getBatchHandlers(modules, options); - return {...defaultHandlers, ...batchHandlers}; - } - return defaultHandlers; + return {...getSequentialHandlers(modules, options), ...getBatchHandlers(modules, options)}; } /** * Default handlers validate gossip messages one by one. * We only have a choice to do batch validation for beacon_attestation topic. */ -function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandlerOpts): DefaultGossipHandlers { - const {chain, config, metrics, events, logger, core, aggregatorTracker} = modules; +function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHandlerOpts): SequentialGossipHandlers { + const {chain, config, metrics, events, logger, core} = modules; async function validateBeaconBlock( signedBlock: SignedBeaconBlock, @@ -144,6 +135,18 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler const blockInputMeta = config.getForkSeq(signedBlock.message.slot) >= ForkSeq.deneb ? blockInputRes.blockInputMeta : {}; + const logCtx = { + slot: slot, + root: blockHex, + currentSlot: chain.clock.currentSlot, + peerId: peerIdStr, + delaySec, + ...blockInputMeta, + recvToValLatency, + }; + + logger.debug("Received gossip block", {...logCtx}); + try { await validateGossipBlock(config, chain, signedBlock, fork); @@ -153,17 +156,7 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler metrics?.gossipBlock.gossipValidation.recvToValidation.observe(recvToValidation); metrics?.gossipBlock.gossipValidation.validationTime.observe(validationTime); - logger.debug("Received gossip block", { - slot: slot, - root: blockHex, - curentSlot: chain.clock.currentSlot, - peerId: peerIdStr, - delaySec, - ...blockInputMeta, - recvToValLatency, - recvToValidation, - validationTime, - }); + logger.debug("Validated gossip block", {...logCtx, recvToValidation, validationTime}); return blockInput; } catch (e) { @@ -456,58 +449,6 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler chain.emitter.emit(routes.events.EventType.attestation, signedAggregateAndProof.message.aggregate); }, - [GossipType.beacon_attestation]: async ({ - gossipData, - topic, - seenTimestampSec, - }: GossipHandlerParamGeneric): Promise => { - const {serializedData, msgSlot} = gossipData; - if (msgSlot == undefined) { - throw Error("msgSlot is undefined for beacon_attestation topic"); - } - const {subnet, fork} = topic; - - // do not deserialize gossipSerializedData here, it's done in validateGossipAttestation only if needed - let validationResult: AttestationValidationResult; - try { - validationResult = await validateGossipAttestation( - fork, - chain, - {attestation: null, serializedData, attSlot: msgSlot}, - subnet - ); - } catch (e) { - if (e instanceof AttestationError && e.action === GossipAction.REJECT) { - chain.persistInvalidSszBytes(ssz.phase0.Attestation.typeName, serializedData, "gossip_reject"); - } - throw e; - } - - // Handler - const {indexedAttestation, attDataRootHex, attestation, committeeIndex} = validationResult; - metrics?.registerGossipUnaggregatedAttestation(seenTimestampSec, indexedAttestation); - - try { - // Node may be subscribe to extra subnets (long-lived random subnets). For those, validate the messages - // but don't add to attestation pool, to save CPU and RAM - if (aggregatorTracker.shouldAggregate(subnet, indexedAttestation.data.slot)) { - const insertOutcome = chain.attestationPool.add(committeeIndex, attestation, attDataRootHex); - metrics?.opPool.attestationPoolInsertOutcome.inc({insertOutcome}); - } - } catch (e) { - logger.error("Error adding unaggregated attestation to pool", {subnet}, e as Error); - } - - if (!options.dontSendGossipAttestationsToForkchoice) { - try { - chain.forkChoice.onAttestation(indexedAttestation, attDataRootHex); - } catch (e) { - logger.debug("Error adding gossip unaggregated attestation to forkchoice", {subnet}, e as Error); - } - } - - chain.emitter.emit(routes.events.EventType.attestation, attestation); - }, [GossipType.attester_slashing]: async ({ gossipData, @@ -658,7 +599,7 @@ function getDefaultHandlers(modules: ValidatorFnsModules, options: GossipHandler /** * For now, only beacon_attestation topic is batched. */ -function getBatchHandlers(modules: ValidatorFnsModules, options: GossipHandlerOpts): Partial { +function getBatchHandlers(modules: ValidatorFnsModules, options: GossipHandlerOpts): BatchGossipHandlers { const {chain, metrics, logger, aggregatorTracker} = modules; return { [GossipType.beacon_attestation]: async ( @@ -739,7 +680,6 @@ export async function validateGossipFnRetryUnknownRoot( blockRoot: Root ): Promise { let unknownBlockRootRetries = 0; - // eslint-disable-next-line no-constant-condition while (true) { try { return await fn(); diff --git a/packages/beacon-node/src/network/processor/gossipQueues/index.ts b/packages/beacon-node/src/network/processor/gossipQueues/index.ts index 347458c91445..dfa5b0dd1973 100644 --- a/packages/beacon-node/src/network/processor/gossipQueues/index.ts +++ b/packages/beacon-node/src/network/processor/gossipQueues/index.ts @@ -1,18 +1,10 @@ import {mapValues} from "@lodestar/utils"; -import {GossipType} from "../../gossip/interface.js"; +import {BatchGossipType, GossipType, SequentialGossipType} from "../../gossip/interface.js"; import {PendingGossipsubMessage} from "../types.js"; import {getGossipAttestationIndex} from "../../../util/sszBytes.js"; import {LinearGossipQueue} from "./linear.js"; -import { - DropType, - GossipQueue, - GossipQueueOpts, - QueueType, - isIndexedGossipQueueAvgTimeOpts, - isIndexedGossipQueueMinSizeOpts, -} from "./types.js"; +import {DropType, GossipQueue, GossipQueueOpts, QueueType, isIndexedGossipQueueMinSizeOpts} from "./types.js"; import {IndexedGossipQueueMinSize} from "./indexed.js"; -import {IndexedGossipQueueAvgTime} from "./indexedAvgTime.js"; /** * In normal condition, the higher this value the more efficient the signature verification. @@ -28,8 +20,8 @@ export const MIN_SIGNATURE_SETS_TO_BATCH_VERIFY = 32; /** * Numbers from https://github.com/sigp/lighthouse/blob/b34a79dc0b02e04441ba01fd0f304d1e203d877d/beacon_node/network/src/beacon_processor/mod.rs#L69 */ -const defaultGossipQueueOpts: { - [K in GossipType]: GossipQueueOpts; +const linearGossipQueueOpts: { + [K in SequentialGossipType]: GossipQueueOpts; } = { // validation gossip block asap [GossipType.beacon_block]: {maxLength: 1024, type: QueueType.FIFO, dropOpts: {type: DropType.count, count: 1}}, @@ -45,15 +37,6 @@ const defaultGossipQueueOpts: { type: QueueType.LIFO, dropOpts: {type: DropType.count, count: 1}, }, - // lighthouse has attestation_queue 16384 and unknown_block_attestation_queue 8192, we use single queue - // this topic may cause node to be overload and drop 100% of lower priority queues - // so we want to drop it by ratio until node is stable enough (queue is empty) - // start with dropping 1% of the queue, then increase 1% more each time. Reset when queue is empty - [GossipType.beacon_attestation]: { - maxLength: 24576, - type: QueueType.LIFO, - dropOpts: {type: DropType.ratio, start: 0.01, step: 0.01}, - }, [GossipType.voluntary_exit]: {maxLength: 4096, type: QueueType.FIFO, dropOpts: {type: DropType.count, count: 1}}, [GossipType.proposer_slashing]: {maxLength: 4096, type: QueueType.FIFO, dropOpts: {type: DropType.count, count: 1}}, [GossipType.attester_slashing]: {maxLength: 4096, type: QueueType.FIFO, dropOpts: {type: DropType.count, count: 1}}, @@ -82,9 +65,11 @@ const defaultGossipQueueOpts: { }; const indexedGossipQueueOpts: { - [K in GossipType]?: GossipQueueOpts; + [K in BatchGossipType]: GossipQueueOpts; } = { [GossipType.beacon_attestation]: { + // lighthouse has attestation_queue 16384 and unknown_block_attestation_queue 8192, we use single queue + // this topic may cause node to be overload and drop 100% of lower priority queues maxLength: 24576, indexFn: (item: PendingGossipsubMessage) => { // Note indexFn is fork agnostic despite changes introduced in Electra @@ -111,19 +96,15 @@ const indexedGossipQueueOpts: { * By topic is too specific, so by type groups all similar objects in the same queue. All in the same won't allow * to customize different queue behaviours per object type (see `gossipQueueOpts`). */ -export function createGossipQueues(beaconAttestationBatchValidation = false): { +export function createGossipQueues(): { [K in GossipType]: GossipQueue; } { - const gossipQueueOpts = beaconAttestationBatchValidation - ? {...defaultGossipQueueOpts, ...indexedGossipQueueOpts} - : defaultGossipQueueOpts; + const gossipQueueOpts = {...linearGossipQueueOpts, ...indexedGossipQueueOpts}; + return mapValues(gossipQueueOpts, (opts) => { if (isIndexedGossipQueueMinSizeOpts(opts)) { return new IndexedGossipQueueMinSize(opts); - } else if (isIndexedGossipQueueAvgTimeOpts(opts)) { - return new IndexedGossipQueueAvgTime(opts); - } else { - return new LinearGossipQueue(opts); } + return new LinearGossipQueue(opts); }); } diff --git a/packages/beacon-node/src/network/processor/gossipQueues/indexed.ts b/packages/beacon-node/src/network/processor/gossipQueues/indexed.ts index 8edba7dfaadb..a7b23b2ac929 100644 --- a/packages/beacon-node/src/network/processor/gossipQueues/indexed.ts +++ b/packages/beacon-node/src/network/processor/gossipQueues/indexed.ts @@ -123,9 +123,8 @@ export class IndexedGossipQueueMinSize = { - items: T[]; - avgRecvTimestampMs: number; -}; - -function listScore(list: ItemList): number { - return list.items.length / Math.max(1000, Date.now() - list.avgRecvTimestampMs); -} - -/** - * An implementation of GossipQueue that tries to run the batch with highest score first. - * TODO: add unit tests - * - index items by indexFn using a map - * - compute avgRecvTimestampMs for each key every time we add new item - * - on next, pick the key with the highest score (check the score function above) - */ -export class IndexedGossipQueueAvgTime implements GossipQueue { - private _length = 0; - private indexedItems: Map> = new Map(); - - constructor(private readonly opts: IndexedGossipQueueOpts) {} - - get length(): number { - return this._length; - } - - get keySize(): number { - return this.indexedItems.size; - } - - clear(): void { - this.indexedItems = new Map(); - this._length = 0; - } - - // not implemented for this gossip queue - getDataAgeMs(): number[] { - return []; - } - - /** - * Add item to gossip queue. If queue is full, drop first item of first key. - * Return number of items dropped - */ - add(item: T): number { - const key = this.opts.indexFn(item); - if (key == null) { - // this comes from getAttDataBase64FromAttestationSerialized() return type - // should not happen - return 0; - } - item.indexed = key; - let list = this.indexedItems.get(key); - if (list == null) { - list = { - items: [], - avgRecvTimestampMs: Date.now(), - }; - this.indexedItems.set(key, list); - } else { - list.avgRecvTimestampMs = (list.avgRecvTimestampMs * list.items.length + Date.now()) / (list.items.length + 1); - list.items.push(item); - } - this._length++; - if (this._length <= this.opts.maxLength) { - return 0; - } - - // overload, need to drop more items - const firstKey = this.indexedItems.keys().next().value as string; - // there should be at least 1 key - if (firstKey == null) { - return 0; - } - const firstList = this.indexedItems.get(firstKey); - // should not happen - if (firstList == null) { - return 0; - } - - const deletedItem = firstList.items.shift(); - if (deletedItem != null) { - this._length--; - if (firstList.items.length === 0) { - this.indexedItems.delete(firstKey); - } - return 1; - } else { - return 0; - } - } - - /** - * Try to get list of items of the same key with highest score - */ - next(): T[] | null { - let maxScore = 0; - let maxScoreKey: string | undefined; - for (const [key, list] of this.indexedItems) { - const score = listScore(list); - if (score > maxScore) { - maxScore = score; - maxScoreKey = key; - } - } - - if (maxScoreKey == null) { - return null; - } - const items = this.indexedItems.get(maxScoreKey)?.items; - if (items == null) { - // should not happen - return null; - } - this.indexedItems.delete(maxScoreKey); - this._length = Math.max(0, this._length - items.length); - return items; - } - - getAll(): T[] { - const items: T[] = []; - for (const list of this.indexedItems.values()) { - items.push(...list.items); - } - return items; - } -} diff --git a/packages/beacon-node/src/network/processor/gossipQueues/linear.ts b/packages/beacon-node/src/network/processor/gossipQueues/linear.ts index 82147910d2b1..57a29054d417 100644 --- a/packages/beacon-node/src/network/processor/gossipQueues/linear.ts +++ b/packages/beacon-node/src/network/processor/gossipQueues/linear.ts @@ -71,13 +71,13 @@ export class LinearGossipQueue implements GossipQueue { // overload, need to drop more items if (this.opts.dropOpts.type === DropType.count) { return this.dropByCount(this.opts.dropOpts.count); - } else { - this.recentDrop = true; - const droppedCount = this.dropByRatio(this._dropRatio); - // increase drop ratio the next time queue is full - this._dropRatio = Math.min(MAX_DROP_RATIO, this._dropRatio + this.opts.dropOpts.step); - return droppedCount; } + + this.recentDrop = true; + const droppedCount = this.dropByRatio(this._dropRatio); + // increase drop ratio the next time queue is full + this._dropRatio = Math.min(MAX_DROP_RATIO, this._dropRatio + this.opts.dropOpts.step); + return droppedCount; } next(): T | null { diff --git a/packages/beacon-node/src/network/processor/gossipQueues/types.ts b/packages/beacon-node/src/network/processor/gossipQueues/types.ts index 448f44c3f5d7..58034e82ed06 100644 --- a/packages/beacon-node/src/network/processor/gossipQueues/types.ts +++ b/packages/beacon-node/src/network/processor/gossipQueues/types.ts @@ -1,4 +1,4 @@ -export type GossipQueueOpts = LinearGossipQueueOpts | IndexedGossipQueueOpts | IndexedGossipQueueMinSizeOpts; +export type GossipQueueOpts = LinearGossipQueueOpts | IndexedGossipQueueMinSizeOpts; export type LinearGossipQueueOpts = { type: QueueType; @@ -25,15 +25,6 @@ export function isIndexedGossipQueueMinSizeOpts(opts: GossipQueueOpts): op ); } -export function isIndexedGossipQueueAvgTimeOpts(opts: GossipQueueOpts): opts is IndexedGossipQueueOpts { - const avgTimeOpts = opts as IndexedGossipQueueMinSizeOpts; - return ( - avgTimeOpts.indexFn !== undefined && - avgTimeOpts.minChunkSize === undefined && - avgTimeOpts.maxChunkSize === undefined - ); -} - export interface GossipQueue { length: number; keySize: number; diff --git a/packages/beacon-node/src/network/processor/index.ts b/packages/beacon-node/src/network/processor/index.ts index 9a1dcfb32fa0..ec0edf54644f 100644 --- a/packages/beacon-node/src/network/processor/index.ts +++ b/packages/beacon-node/src/network/processor/index.ts @@ -172,7 +172,7 @@ export class NetworkProcessor { this.metrics = metrics; this.logger = logger; this.events = events; - this.gossipQueues = createGossipQueues(this.opts.beaconAttestationBatchValidation); + this.gossipQueues = createGossipQueues(); this.gossipTopicConcurrency = mapValues(this.gossipQueues, () => 0); this.gossipValidatorFn = getGossipValidatorFn(modules.gossipHandlers ?? getGossipHandlers(modules, opts), modules); this.gossipValidatorBatchFn = getGossipValidatorBatchFn( @@ -396,7 +396,9 @@ export class NetworkProcessor { if (item) { this.gossipTopicConcurrency[topic] += numMessages; this.processPendingGossipsubMessage(item) - .finally(() => (this.gossipTopicConcurrency[topic] -= numMessages)) + .finally(() => { + this.gossipTopicConcurrency[topic] -= numMessages; + }) .catch((e) => this.logger.error("processGossipAttestations must not throw", {}, e)); jobsSubmitted += numMessages; diff --git a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts index 4dae4831d716..3d71087c8fd8 100644 --- a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts +++ b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts @@ -1,11 +1,11 @@ import {ChainForkConfig} from "@lodestar/config"; -import {deneb, Epoch, phase0, SignedBeaconBlock, Slot} from "@lodestar/types"; +import {deneb, Epoch, phase0, SignedBeaconBlock, Slot, WithBytes} from "@lodestar/types"; import {ForkSeq} from "@lodestar/params"; import {computeEpochAtSlot} from "@lodestar/state-transition"; import {BlobsSource, BlockInput, BlockSource, getBlockInput, BlockInputDataBlobs} from "../../chain/blocks/types.js"; import {PeerIdStr} from "../../util/peerId.js"; -import {INetwork, WithBytes} from "../interface.js"; +import {INetwork} from "../interface.js"; export async function beaconBlocksMaybeBlobsByRange( config: ChainForkConfig, @@ -37,7 +37,7 @@ export async function beaconBlocksMaybeBlobsByRange( } // Only request blobs if they are recent enough - else if (computeEpochAtSlot(startSlot) >= currentEpoch - config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS) { + if (computeEpochAtSlot(startSlot) >= currentEpoch - config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS) { const [allBlocks, allBlobSidecars] = await Promise.all([ network.sendBeaconBlocksByRange(peerId, request), network.sendBlobSidecarsByRange(peerId, request), @@ -47,9 +47,7 @@ export async function beaconBlocksMaybeBlobsByRange( } // Post Deneb but old blobs - else { - throw Error("Cannot sync blobs outside of blobs prune window"); - } + throw Error("Cannot sync blobs outside of blobs prune window"); } // Assumes that the blobs are in the same sequence as blocks, doesn't require block to be sorted @@ -80,6 +78,7 @@ export function matchBlockWithBlobs( let blobSidecar: deneb.BlobSidecar; while ( + // biome-ignore lint/suspicious/noAssignInExpressions: (blobSidecar = allBlobSidecars[blobSideCarIndex])?.signedBlockHeader.message.slot === block.data.message.slot ) { blobSidecars.push(blobSidecar); diff --git a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts index 2b802ab1edd9..3d121156d8e6 100644 --- a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts +++ b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRoot.ts @@ -1,7 +1,7 @@ -import {fromHexString} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {phase0, deneb} from "@lodestar/types"; +import {phase0, deneb, SignedBeaconBlock} from "@lodestar/types"; import {ForkSeq} from "@lodestar/params"; +import {fromHex} from "@lodestar/utils"; import { BlockInput, BlockInputType, @@ -64,9 +64,13 @@ export async function unavailableBeaconBlobsByRoot( } // resolve the block if thats unavailable - let block, blobsCache, blockBytes, resolveAvailability, cachedData; + let block: SignedBeaconBlock, + blobsCache: NullBlockInput["cachedData"]["blobsCache"], + blockBytes: Uint8Array | null, + resolveAvailability: NullBlockInput["cachedData"]["resolveAvailability"], + cachedData: NullBlockInput["cachedData"]; if (unavailableBlockInput.block === null) { - const allBlocks = await network.sendBeaconBlocksByRoot(peerId, [fromHexString(unavailableBlockInput.blockRootHex)]); + const allBlocks = await network.sendBeaconBlocksByRoot(peerId, [fromHex(unavailableBlockInput.blockRootHex)]); block = allBlocks[0].data; blockBytes = allBlocks[0].bytes; cachedData = unavailableBlockInput.cachedData; diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts index d14e0945e977..3b50304eb50c 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts @@ -24,8 +24,7 @@ export async function* onLightClientBootstrap(requestBody: Root, chain: IBeaconC } catch (e) { if ((e as LightClientServerError).type?.code === LightClientServerErrorCode.RESOURCE_UNAVAILABLE) { throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, (e as Error).message); - } else { - throw new ResponseError(RespStatus.SERVER_ERROR, (e as Error).message); } + throw new ResponseError(RespStatus.SERVER_ERROR, (e as Error).message); } } diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts index 2468b0b64f4b..4764c6f198f7 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts @@ -9,12 +9,12 @@ export async function* onLightClientFinalityUpdate(chain: IBeaconChain): AsyncIt const update = chain.lightClientServer.getFinalityUpdate(); if (update === null) { throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "No latest finality update available"); - } else { - const fork = chain.config.getForkName(update.signatureSlot); - const type = responseSszTypeByMethod[ReqRespMethod.LightClientFinalityUpdate](fork, 0); - yield { - data: type.serialize(update), - fork, - }; } + + const fork = chain.config.getForkName(update.signatureSlot); + const type = responseSszTypeByMethod[ReqRespMethod.LightClientFinalityUpdate](fork, 0); + yield { + data: type.serialize(update), + fork, + }; } diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts index ba8371910c02..4c030a8e4174 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts @@ -9,12 +9,12 @@ export async function* onLightClientOptimisticUpdate(chain: IBeaconChain): Async const update = chain.lightClientServer.getOptimisticUpdate(); if (update === null) { throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "No latest optimistic update available"); - } else { - const fork = chain.config.getForkName(update.signatureSlot); - const type = responseSszTypeByMethod[ReqRespMethod.LightClientOptimisticUpdate](fork, 0); - yield { - data: type.serialize(update), - fork, - }; } + + const fork = chain.config.getForkName(update.signatureSlot); + const type = responseSszTypeByMethod[ReqRespMethod.LightClientOptimisticUpdate](fork, 0); + yield { + data: type.serialize(update), + fork, + }; } diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts index eb0e3c3d3f4e..89466eca6c21 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts @@ -31,9 +31,8 @@ export async function* onLightClientUpdatesByRange( } catch (e) { if ((e as LightClientServerError).type?.code === LightClientServerErrorCode.RESOURCE_UNAVAILABLE) { throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, (e as Error).message); - } else { - throw new ResponseError(RespStatus.SERVER_ERROR, (e as Error).message); } + throw new ResponseError(RespStatus.SERVER_ERROR, (e as Error).message); } } } diff --git a/packages/beacon-node/src/network/reqresp/protocols.ts b/packages/beacon-node/src/network/reqresp/protocols.ts index a0fa9576c93c..e63df4b1341b 100644 --- a/packages/beacon-node/src/network/reqresp/protocols.ts +++ b/packages/beacon-node/src/network/reqresp/protocols.ts @@ -3,8 +3,6 @@ import {ForkDigestContext} from "@lodestar/config"; import {ProtocolNoHandler, ReqRespMethod, Version, requestSszTypeByMethod, responseSszTypeByMethod} from "./types.js"; import {rateLimitQuotas} from "./rateLimit.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - export const Goodbye = toProtocol({ method: ReqRespMethod.Goodbye, version: Version.V1, diff --git a/packages/beacon-node/src/network/reqresp/rateLimit.ts b/packages/beacon-node/src/network/reqresp/rateLimit.ts index 881ab36bc05d..b6dacd6ba8e5 100644 --- a/packages/beacon-node/src/network/reqresp/rateLimit.ts +++ b/packages/beacon-node/src/network/reqresp/rateLimit.ts @@ -76,7 +76,7 @@ function getRequestCountFn( return (reqData: Uint8Array) => { try { return (type && fn(type.deserialize(reqData))) ?? 1; - } catch (e) { + } catch (_e) { return 1; } }; diff --git a/packages/beacon-node/src/network/reqresp/score.ts b/packages/beacon-node/src/network/reqresp/score.ts index c74b645c9909..647481162c78 100644 --- a/packages/beacon-node/src/network/reqresp/score.ts +++ b/packages/beacon-node/src/network/reqresp/score.ts @@ -8,7 +8,6 @@ import {ReqRespMethod} from "./types.js"; * https://github.com/libp2p/js-libp2p/blob/6350a187c7c207086e42436ccbcabd59af6f5e3d/src/errors.js#L32 */ const libp2pErrorCodes = { - // eslint-disable-next-line @typescript-eslint/naming-convention ERR_UNSUPPORTED_PROTOCOL: "ERR_UNSUPPORTED_PROTOCOL", }; diff --git a/packages/beacon-node/src/network/reqresp/types.ts b/packages/beacon-node/src/network/reqresp/types.ts index 36fa0a4f2632..96ae1558ec07 100644 --- a/packages/beacon-node/src/network/reqresp/types.ts +++ b/packages/beacon-node/src/network/reqresp/types.ts @@ -1,7 +1,20 @@ 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, sszTypesFor} from "@lodestar/types"; +import { + LightClientBootstrap, + LightClientFinalityUpdate, + LightClientOptimisticUpdate, + LightClientUpdate, + Metadata, + Root, + SignedBeaconBlock, + altair, + deneb, + phase0, + ssz, + sszTypesFor, +} from "@lodestar/types"; export type ProtocolNoHandler = Omit; @@ -48,10 +61,10 @@ type ResponseBodyByMethod = { [ReqRespMethod.BeaconBlocksByRoot]: SignedBeaconBlock; [ReqRespMethod.BlobSidecarsByRange]: deneb.BlobSidecar; [ReqRespMethod.BlobSidecarsByRoot]: deneb.BlobSidecar; - [ReqRespMethod.LightClientBootstrap]: altair.LightClientBootstrap; - [ReqRespMethod.LightClientUpdatesByRange]: altair.LightClientUpdate; - [ReqRespMethod.LightClientFinalityUpdate]: altair.LightClientFinalityUpdate; - [ReqRespMethod.LightClientOptimisticUpdate]: altair.LightClientOptimisticUpdate; + [ReqRespMethod.LightClientBootstrap]: LightClientBootstrap; + [ReqRespMethod.LightClientUpdatesByRange]: LightClientUpdate; + [ReqRespMethod.LightClientFinalityUpdate]: LightClientFinalityUpdate; + [ReqRespMethod.LightClientOptimisticUpdate]: LightClientOptimisticUpdate; }; /** Request SSZ type for each method and ForkName */ @@ -77,16 +90,16 @@ export type ResponseTypeGetter = (fork: ForkName, version: number) => Type const blocksResponseType: ResponseTypeGetter = (fork, version) => { if (version === Version.V1) { return ssz.phase0.SignedBeaconBlock; - } else { - return ssz[fork].SignedBeaconBlock; } + + return ssz[fork].SignedBeaconBlock; }; export const responseSszTypeByMethod: {[K in ReqRespMethod]: ResponseTypeGetter} = { [ReqRespMethod.Status]: () => ssz.phase0.Status, [ReqRespMethod.Goodbye]: () => ssz.phase0.Goodbye, [ReqRespMethod.Ping]: () => ssz.phase0.Ping, - [ReqRespMethod.Metadata]: (_, version) => (version == Version.V1 ? ssz.phase0.Metadata : ssz.altair.Metadata), + [ReqRespMethod.Metadata]: (_, version) => (version === Version.V1 ? ssz.phase0.Metadata : ssz.altair.Metadata), [ReqRespMethod.BeaconBlocksByRange]: blocksResponseType, [ReqRespMethod.BeaconBlocksByRoot]: blocksResponseType, [ReqRespMethod.BlobSidecarsByRange]: () => ssz.deneb.BlobSidecar, @@ -101,9 +114,8 @@ export const responseSszTypeByMethod: {[K in ReqRespMethod]: ResponseTypeGetter< function onlyLightclientFork(fork: ForkName): ForkLightClient { if (isForkLightClient(fork)) { return fork; - } else { - throw Error(`Not a lightclient fork ${fork}`); } + throw Error(`Not a lightclient fork ${fork}`); } export type RequestTypedContainer = { diff --git a/packages/beacon-node/src/network/reqresp/utils/collect.ts b/packages/beacon-node/src/network/reqresp/utils/collect.ts index 06f3cfc36806..9818b1921f8e 100644 --- a/packages/beacon-node/src/network/reqresp/utils/collect.ts +++ b/packages/beacon-node/src/network/reqresp/utils/collect.ts @@ -1,7 +1,7 @@ import {Type} from "@chainsafe/ssz"; import {ResponseIncoming, RequestErrorCode, RequestError} from "@lodestar/reqresp"; +import {WithBytes} from "@lodestar/types"; import {ResponseTypeGetter} from "../types.js"; -import {WithBytes} from "../../interface.js"; /** * Sink for `*`, from diff --git a/packages/beacon-node/src/network/reqresp/utils/collectSequentialBlocksInRange.ts b/packages/beacon-node/src/network/reqresp/utils/collectSequentialBlocksInRange.ts index c2cf0ad16ea0..2709cb3f64a9 100644 --- a/packages/beacon-node/src/network/reqresp/utils/collectSequentialBlocksInRange.ts +++ b/packages/beacon-node/src/network/reqresp/utils/collectSequentialBlocksInRange.ts @@ -1,7 +1,6 @@ import {ResponseIncoming} from "@lodestar/reqresp"; import {LodestarError} from "@lodestar/utils"; -import {phase0, SignedBeaconBlock} from "@lodestar/types"; -import {WithBytes} from "../../interface.js"; +import {phase0, SignedBeaconBlock, WithBytes} from "@lodestar/types"; import {ReqRespMethod, responseSszTypeByMethod} from "../types.js"; import {sszDeserializeResponse} from "./collect.js"; diff --git a/packages/beacon-node/src/network/util.ts b/packages/beacon-node/src/network/util.ts index dd317b5a0427..13eb13331f74 100644 --- a/packages/beacon-node/src/network/util.ts +++ b/packages/beacon-node/src/network/util.ts @@ -15,7 +15,7 @@ export function prettyPrintPeerIdStr(id: PeerIdStr): string { */ // Compat function for efficiency reasons export function getConnectionsMap(libp2p: Libp2p): Map { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return + // biome-ignore lint/complexity/useLiteralKeys: `map` is a private attribute return libp2p.services.components.connectionManager.getConnectionsMap()["map"]; } diff --git a/packages/beacon-node/src/node/nodejs.ts b/packages/beacon-node/src/node/nodejs.ts index 088541a6b5d5..701ab0fde070 100644 --- a/packages/beacon-node/src/node/nodejs.ts +++ b/packages/beacon-node/src/node/nodejs.ts @@ -305,7 +305,7 @@ export class BeaconNode { void runNodeNotifier({network, chain, sync, config, logger, signal}); - return new this({ + return new BeaconNode({ opts, config, db, diff --git a/packages/beacon-node/src/node/notifier.ts b/packages/beacon-node/src/node/notifier.ts index 6b9a29817158..20a359a45c49 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 msPerHalfSlot ? msToNextSlot - msPerHalfSlot : msToNextSlot + msPerHalfSlot; - } else { - // after the 1st time always wait until middle of next clock slot - return msToNextSlot + msPerHalfSlot; } + // after the 1st time always wait until middle of next clock slot + return msToNextSlot + msPerHalfSlot; } function getHeadExecutionInfo( @@ -181,26 +179,25 @@ function getHeadExecutionInfo( ): string[] { if (clockEpoch < config.BELLATRIX_FORK_EPOCH) { return []; - } else { - const executionStatusStr = headInfo.executionStatus.toLowerCase(); - - // Add execution status to notifier only if head is on/post bellatrix - if (isExecutionCachedStateType(headState)) { - if (isMergeTransitionComplete(headState)) { - const executionPayloadHashInfo = - headInfo.executionStatus !== ExecutionStatus.PreMerge ? headInfo.executionPayloadBlockHash : "empty"; - const executionPayloadNumberInfo = - headInfo.executionStatus !== ExecutionStatus.PreMerge ? headInfo.executionPayloadNumber : NaN; - return [ - `exec-block: ${executionStatusStr}(${executionPayloadNumberInfo} ${prettyBytesShort( - executionPayloadHashInfo - )})`, - ]; - } else { - return [`exec-block: ${executionStatusStr}`]; - } - } else { - return []; + } + + const executionStatusStr = headInfo.executionStatus.toLowerCase(); + + // Add execution status to notifier only if head is on/post bellatrix + if (isExecutionCachedStateType(headState)) { + if (isMergeTransitionComplete(headState)) { + const executionPayloadHashInfo = + headInfo.executionStatus !== ExecutionStatus.PreMerge ? headInfo.executionPayloadBlockHash : "empty"; + const executionPayloadNumberInfo = + headInfo.executionStatus !== ExecutionStatus.PreMerge ? headInfo.executionPayloadNumber : NaN; + return [ + `exec-block: ${executionStatusStr}(${executionPayloadNumberInfo} ${prettyBytesShort( + executionPayloadHashInfo + )})`, + ]; } + return [`exec-block: ${executionStatusStr}`]; } + + return []; } diff --git a/packages/beacon-node/src/node/options.ts b/packages/beacon-node/src/node/options.ts index 475a4debee63..e587e58ec127 100644 --- a/packages/beacon-node/src/node/options.ts +++ b/packages/beacon-node/src/node/options.ts @@ -1,5 +1,5 @@ import {defaultApiOptions, ApiOptions} from "../api/options.js"; -import {defaultChainOptions, IChainOptions} from "../chain/options.js"; +import {defaultChainOptions, IChainOptions, StateArchiveMode, DEFAULT_STATE_ARCHIVE_MODE} from "../chain/options.js"; import {defaultDbOptions, DatabaseOptions} from "../db/options.js"; import {defaultEth1Options, Eth1Options} from "../eth1/options.js"; import {defaultMetricsOptions, MetricsOptions} from "../metrics/options.js"; @@ -18,7 +18,7 @@ import { export {allNamespaces} from "../api/rest/index.js"; // Re-export to use as default values in CLI args -export {defaultExecutionEngineHttpOpts, defaultExecutionBuilderHttpOpts}; +export {defaultExecutionEngineHttpOpts, defaultExecutionBuilderHttpOpts, StateArchiveMode, DEFAULT_STATE_ARCHIVE_MODE}; export interface IBeaconNodeOptions { api: ApiOptions; diff --git a/packages/beacon-node/src/node/utils/interop/state.ts b/packages/beacon-node/src/node/utils/interop/state.ts index 6528bd392bc7..fe26afef2013 100644 --- a/packages/beacon-node/src/node/utils/interop/state.ts +++ b/packages/beacon-node/src/node/utils/interop/state.ts @@ -20,6 +20,7 @@ export type InteropStateOpts = { withEth1Credentials?: boolean; }; +// TODO: (@matthewkeil) - Only used by initDevState. Consider combining into that function export function getInteropState( config: ChainForkConfig, { diff --git a/packages/beacon-node/src/node/utils/state.ts b/packages/beacon-node/src/node/utils/state.ts index 25bd77c82274..05da7042eef4 100644 --- a/packages/beacon-node/src/node/utils/state.ts +++ b/packages/beacon-node/src/node/utils/state.ts @@ -5,6 +5,9 @@ import {IBeaconDb} from "../../db/index.js"; import {interopDeposits} from "./interop/deposits.js"; import {getInteropState, InteropStateOpts} from "./interop/state.js"; +/** + * Builds state for `dev` command, for sim testing and some other tests + */ export function initDevState( config: ChainForkConfig, validatorCount: number, diff --git a/packages/beacon-node/src/sync/backfill/backfill.ts b/packages/beacon-node/src/sync/backfill/backfill.ts index 38258fde07fd..9612cf23b615 100644 --- a/packages/beacon-node/src/sync/backfill/backfill.ts +++ b/packages/beacon-node/src/sync/backfill/backfill.ts @@ -1,6 +1,6 @@ -import {EventEmitter} from "events"; +import {EventEmitter} from "node:events"; import {StrictEventEmitter} from "strict-event-emitter-types"; -import {BeaconStateAllForks, blockToHeader} from "@lodestar/state-transition"; +import {BeaconStateAllForks, blockToHeader, computeAnchorCheckpoint} from "@lodestar/state-transition"; import {BeaconConfig, ChainForkConfig} from "@lodestar/config"; import {phase0, Root, SignedBeaconBlock, Slot, ssz} from "@lodestar/types"; import {ErrorAborted, Logger, sleep, toRootHex} from "@lodestar/utils"; @@ -13,9 +13,8 @@ import {INetwork, NetworkEvent, NetworkEventData, PeerAction} from "../../networ import {ItTrigger} from "../../util/itTrigger.js"; import {PeerIdStr} from "../../util/peerId.js"; import {shuffleOne} from "../../util/shuffle.js"; -import {Metrics} from "../../metrics/metrics"; +import {Metrics} from "../../metrics/metrics.js"; import {byteArrayEquals} from "../../util/bytes.js"; -import {computeAnchorCheckpoint} from "../../chain/initState.js"; import {verifyBlockProposerSignature, verifyBlockSequence, BackfillBlockHeader, BackfillBlock} from "./verify.js"; import {BackfillSyncError, BackfillSyncErrorCode} from "./errors.js"; /** @@ -263,7 +262,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} // Load a previous finalized or wsCheckpoint slot from DB below anchorSlot const prevFinalizedCheckpointBlock = await extractPreviousFinOrWsCheckpoint(config, db, anchorSlot, logger); - return new this(opts, { + return new BackfillSync(opts, { syncAnchor, backfillStartFromSlot, backfillRangeWrittenSlot, @@ -458,6 +457,7 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} this.status = BackfillSyncStatus.aborted; break; case BackfillSyncErrorCode.NOT_ANCHORED: + // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here case BackfillSyncErrorCode.NOT_LINEAR: // Lets try to jump directly to the parent of this anchorBlock as previous // (segment) of blocks could be orphaned/missed @@ -675,12 +675,13 @@ export class BackfillSync extends (EventEmitter as {new (): BackfillSyncEmitter} ); // If possible, read back till anchorBlock > this.prevFinalizedCheckpointBlock - let parentBlock, + let parentBlock: SignedBeaconBlock | null, backCount = 1; let isPrevFinWsConfirmedAnchorParent = false; while ( backCount !== this.opts.backfillBatchSize && + // biome-ignore lint/suspicious/noAssignInExpressions: (parentBlock = await this.db.blockArchive.getByRoot(anchorBlock.message.parentRoot)) ) { // Before moving anchorBlock back, we need check for prevFinalizedCheckpointBlock diff --git a/packages/beacon-node/src/sync/backfill/verify.ts b/packages/beacon-node/src/sync/backfill/verify.ts index 462762a5576f..715cc6621253 100644 --- a/packages/beacon-node/src/sync/backfill/verify.ts +++ b/packages/beacon-node/src/sync/backfill/verify.ts @@ -1,9 +1,8 @@ import {CachedBeaconStateAllForks, ISignatureSet, getBlockProposerSignatureSet} from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; -import {Root, ssz, Slot, SignedBeaconBlock} from "@lodestar/types"; +import {Root, ssz, Slot, SignedBeaconBlock, WithBytes} from "@lodestar/types"; import {GENESIS_SLOT} from "@lodestar/params"; import {IBlsVerifier} from "../../chain/bls/index.js"; -import {WithBytes} from "../../network/interface.js"; import {BackfillSyncError, BackfillSyncErrorCode} from "./errors.js"; export type BackfillBlockHeader = { diff --git a/packages/beacon-node/src/sync/range/range.ts b/packages/beacon-node/src/sync/range/range.ts index 887a86a3aaf2..51e3a5d0f182 100644 --- a/packages/beacon-node/src/sync/range/range.ts +++ b/packages/beacon-node/src/sync/range/range.ts @@ -1,4 +1,4 @@ -import {EventEmitter} from "events"; +import {EventEmitter} from "node:events"; import {StrictEventEmitter} from "strict-event-emitter-types"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {BeaconConfig} from "@lodestar/config"; @@ -150,17 +150,15 @@ export class RangeSync extends (EventEmitter as {new (): RangeSyncEmitter}) { if (chain.isSyncing) { if (chain.syncType === RangeSyncType.Finalized) { return {status: RangeSyncStatus.Finalized, target: chain.target}; - } else { - syncingHeadTargets.push(chain.target); } + syncingHeadTargets.push(chain.target); } } if (syncingHeadTargets.length > 0) { return {status: RangeSyncStatus.Head, targets: syncingHeadTargets}; - } else { - return {status: RangeSyncStatus.Idle}; } + return {status: RangeSyncStatus.Idle}; } /** Full debug state for lodestar API */ diff --git a/packages/beacon-node/src/sync/range/utils/batches.ts b/packages/beacon-node/src/sync/range/utils/batches.ts index 734c84800b69..c8490ade6317 100644 --- a/packages/beacon-node/src/sync/range/utils/batches.ts +++ b/packages/beacon-node/src/sync/range/utils/batches.ts @@ -113,7 +113,7 @@ export function isSyncChainDone(batches: Batch[], lastEpochWithProcessBlocks: Ep if (lastAwaitingValidation) { return batchStartEpochIsAfterSlot(lastAwaitingValidation.startEpoch + EPOCHS_PER_BATCH, targetSlot); - } else { - return batchStartEpochIsAfterSlot(lastEpochWithProcessBlocks, targetSlot); } + + return batchStartEpochIsAfterSlot(lastEpochWithProcessBlocks, targetSlot); } diff --git a/packages/beacon-node/src/sync/sync.ts b/packages/beacon-node/src/sync/sync.ts index cc8ddc6eb499..94eea73e1ef8 100644 --- a/packages/beacon-node/src/sync/sync.ts +++ b/packages/beacon-node/src/sync/sync.ts @@ -88,7 +88,9 @@ export class BeaconSync implements IBeaconSync { getSyncStatus(): SyncingStatus { const currentSlot = this.chain.clock.currentSlot; - const elOffline = this.chain.executionEngine.state === ExecutionEngineState.OFFLINE; + const elOffline = + this.chain.executionEngine.state === ExecutionEngineState.OFFLINE || + this.chain.executionEngine.state === ExecutionEngineState.AUTH_FAILED; // If we are pre/at genesis, signal ready if (currentSlot <= GENESIS_SLOT) { @@ -99,31 +101,31 @@ export class BeaconSync implements IBeaconSync { isOptimistic: false, elOffline, }; - } else { - const head = this.chain.forkChoice.getHead(); - - switch (this.state) { - case SyncState.SyncingFinalized: - case SyncState.SyncingHead: - case SyncState.Stalled: - return { - headSlot: head.slot, - syncDistance: currentSlot - head.slot, - isSyncing: true, - isOptimistic: isOptimisticBlock(head), - elOffline, - }; - case SyncState.Synced: - return { - headSlot: head.slot, - syncDistance: 0, - isSyncing: false, - isOptimistic: isOptimisticBlock(head), - elOffline, - }; - default: - throw new Error("Node is stopped, cannot get sync status"); - } + } + + const head = this.chain.forkChoice.getHead(); + + switch (this.state) { + case SyncState.SyncingFinalized: + case SyncState.SyncingHead: + case SyncState.Stalled: + return { + headSlot: head.slot, + syncDistance: currentSlot - head.slot, + isSyncing: true, + isOptimistic: isOptimisticBlock(head), + elOffline, + }; + case SyncState.Synced: + return { + headSlot: head.slot, + syncDistance: 0, + isSyncing: false, + isOptimistic: isOptimisticBlock(head), + elOffline, + }; + default: + throw new Error("Node is stopped, cannot get sync status"); } } @@ -163,6 +165,8 @@ export class BeaconSync implements IBeaconSync { return SyncState.SyncingHead; case RangeSyncStatus.Idle: return SyncState.Stalled; + default: + throw new Error("Unreachable code"); } } diff --git a/packages/beacon-node/src/sync/unknownBlock.ts b/packages/beacon-node/src/sync/unknownBlock.ts index d985847d450f..003b035a898d 100644 --- a/packages/beacon-node/src/sync/unknownBlock.ts +++ b/packages/beacon-node/src/sync/unknownBlock.ts @@ -1,6 +1,5 @@ -import {fromHexString} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {Logger, pruneSetToMax, toRootHex} from "@lodestar/utils"; +import {Logger, fromHex, pruneSetToMax, toRootHex} from "@lodestar/utils"; import {Root, RootHex, deneb} from "@lodestar/types"; import {INTERVALS_PER_SLOT} from "@lodestar/params"; import {sleep} from "@lodestar/utils"; @@ -16,7 +15,7 @@ import { beaconBlocksMaybeBlobsByRoot, unavailableBeaconBlobsByRoot, } from "../network/reqresp/beaconBlocksMaybeBlobsByRoot.js"; -import {wrapError} from "../util/wrapError.js"; +import {Result, wrapError} from "../util/wrapError.js"; import {PendingBlock, PendingBlockStatus, PendingBlockType} from "./interface.js"; import {getDescendantBlocks, getAllDescendantBlocks, getUnknownAndAncestorBlocks} from "./utils/pendingBlocksTree.js"; import {SyncOptions} from "./options.js"; @@ -169,7 +168,7 @@ export class UnknownBlockSync { blockInputOrRootHex: RootHex | BlockInput | NullBlockInput, peerIdStr?: string ): Exclude { - let blockRootHex; + let blockRootHex: RootHex; let blockInput: BlockInput | NullBlockInput | null; let unknownBlockType: Exclude; @@ -286,9 +285,9 @@ export class UnknownBlockSync { block.status = PendingBlockStatus.fetching; - let res; + let res: Result<{blockInput: BlockInput; peerIdStr: string}>; if (block.blockInput === null) { - res = await wrapError(this.fetchUnknownBlockRoot(fromHexString(block.blockRootHex), connectedPeers)); + res = await wrapError(this.fetchUnknownBlockRoot(fromHex(block.blockRootHex), connectedPeers)); } else { res = await wrapError(this.fetchUnavailableBlockInput(block.blockInput, connectedPeers)); } @@ -494,9 +493,8 @@ export class UnknownBlockSync { if (lastError) { lastError.message = `Error fetching UnknownBlockRoot after ${MAX_ATTEMPTS_PER_BLOCK} attempts: ${lastError.message}`; throw lastError; - } else { - throw Error(`Error fetching UnknownBlockRoot after ${MAX_ATTEMPTS_PER_BLOCK}: unknown error`); } + throw Error(`Error fetching UnknownBlockRoot after ${MAX_ATTEMPTS_PER_BLOCK}: unknown error`); } /** @@ -512,14 +510,14 @@ export class UnknownBlockSync { } const shuffledPeers = shuffle(connectedPeers); - let blockRootHex; - let pendingBlobs; - let blobKzgCommitmentsLen; - let blockRoot; + let blockRootHex: RootHex; + let pendingBlobs: number | undefined; + let blobKzgCommitmentsLen: number | undefined; + let blockRoot: Uint8Array; if (unavailableBlockInput.block === null) { blockRootHex = unavailableBlockInput.blockRootHex; - blockRoot = fromHexString(blockRootHex); + blockRoot = fromHex(blockRootHex); } else { const unavailableBlock = unavailableBlockInput.block; blockRoot = this.config @@ -570,9 +568,9 @@ export class UnknownBlockSync { if (lastError) { lastError.message = `Error fetching UnavailableBlockInput after ${MAX_ATTEMPTS_PER_BLOCK} attempts: ${lastError.message}`; throw lastError; - } else { - throw Error(`Error fetching UnavailableBlockInput after ${MAX_ATTEMPTS_PER_BLOCK}: unknown error`); } + + throw Error(`Error fetching UnavailableBlockInput after ${MAX_ATTEMPTS_PER_BLOCK}: unknown error`); } /** diff --git a/packages/beacon-node/src/sync/utils/remoteSyncType.ts b/packages/beacon-node/src/sync/utils/remoteSyncType.ts index 87cca86375f0..247269ab409a 100644 --- a/packages/beacon-node/src/sync/utils/remoteSyncType.ts +++ b/packages/beacon-node/src/sync/utils/remoteSyncType.ts @@ -49,36 +49,33 @@ export function getPeerSyncType( return PeerSyncType.Behind; } - // - else if (remote.finalizedEpoch > local.finalizedEpoch) { + if (remote.finalizedEpoch > local.finalizedEpoch) { if ( // Peer is in next epoch, and head is within range => SYNCED - (local.finalizedEpoch + 1 == remote.finalizedEpoch && + (local.finalizedEpoch + 1 === remote.finalizedEpoch && withinRangeOf(remote.headSlot, local.headSlot, slotImportTolerance)) || // Peer's head is known => SYNCED forkChoice.hasBlock(remote.headRoot) ) { return PeerSyncType.FullySynced; - } else { - return PeerSyncType.Advanced; } + return PeerSyncType.Advanced; } // remote.finalizedEpoch == local.finalizedEpoch - else { - // NOTE: if a peer has our same `finalizedEpoch` with a different `finalized_root` - // they are not considered relevant and won't be propagated to sync. - // Check if the peer is the peer is inside the tolerance range to be considered synced. - if (remote.headSlot < nearRangeStart) { - return PeerSyncType.Behind; - } else if (remote.headSlot > nearRangeEnd && !forkChoice.hasBlock(remote.headRoot)) { - // This peer has a head ahead enough of ours and we have no knowledge of their best block. - return PeerSyncType.Advanced; - } else { - // This peer is either in the tolerance range, or ahead us with an already rejected block. - return PeerSyncType.FullySynced; - } + // NOTE: if a peer has our same `finalizedEpoch` with a different `finalized_root` + // they are not considered relevant and won't be propagated to sync. + // Check if the peer is the peer is inside the tolerance range to be considered synced. + if (remote.headSlot < nearRangeStart) { + return PeerSyncType.Behind; + } + + if (remote.headSlot > nearRangeEnd && !forkChoice.hasBlock(remote.headRoot)) { + // This peer has a head ahead enough of ours and we have no knowledge of their best block. + return PeerSyncType.Advanced; } + // This peer is either in the tolerance range, or ahead us with an already rejected block. + return PeerSyncType.FullySynced; } export enum RangeSyncType { @@ -99,9 +96,8 @@ export const rangeSyncTypes = Object.keys(RangeSyncType) as RangeSyncType[]; export function getRangeSyncType(local: phase0.Status, remote: phase0.Status, forkChoice: IForkChoice): RangeSyncType { if (remote.finalizedEpoch > local.finalizedEpoch && !forkChoice.hasBlock(remote.finalizedRoot)) { return RangeSyncType.Finalized; - } else { - return RangeSyncType.Head; } + return RangeSyncType.Head; } export function getRangeSyncTarget( @@ -134,16 +130,15 @@ export function getRangeSyncTarget( root: remote.finalizedRoot, }, }; - } else { - return { - syncType: RangeSyncType.Head, - // The new peer has the same finalized (earlier filters should prevent a peer with an - // earlier finalized chain from reaching here). - startEpoch: Math.min(computeEpochAtSlot(local.headSlot), remote.finalizedEpoch), - target: { - slot: remote.headSlot, - root: remote.headRoot, - }, - }; } + return { + syncType: RangeSyncType.Head, + // The new peer has the same finalized (earlier filters should prevent a peer with an + // earlier finalized chain from reaching here). + startEpoch: Math.min(computeEpochAtSlot(local.headSlot), remote.finalizedEpoch), + target: { + slot: remote.headSlot, + root: remote.headRoot, + }, + }; } diff --git a/packages/beacon-node/src/util/asyncIterableToEvents.ts b/packages/beacon-node/src/util/asyncIterableToEvents.ts index 0468ca2b431b..836983d8e4f0 100644 --- a/packages/beacon-node/src/util/asyncIterableToEvents.ts +++ b/packages/beacon-node/src/util/asyncIterableToEvents.ts @@ -46,7 +46,6 @@ export class AsyncIterableBridgeCaller { } getAsyncIterable(callArgs: Args): AsyncIterable { - // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; return { @@ -67,7 +66,6 @@ export class AsyncIterableBridgeCaller { return { async next() { - // eslint-disable-next-line no-constant-condition while (true) { const item = req.items.shift(); if (item !== null) { diff --git a/packages/beacon-node/src/util/binarySearch.ts b/packages/beacon-node/src/util/binarySearch.ts index db7c03af50b7..459154eb0427 100644 --- a/packages/beacon-node/src/util/binarySearch.ts +++ b/packages/beacon-node/src/util/binarySearch.ts @@ -1,5 +1,5 @@ export function binarySearchLte(items: T[], value: number, getter: (item: T) => number): T { - if (items.length == 0) { + if (items.length === 0) { throw new ErrorNoValues(); } diff --git a/packages/beacon-node/src/util/bitArray.ts b/packages/beacon-node/src/util/bitArray.ts index 364110fe958f..3411886de3ca 100644 --- a/packages/beacon-node/src/util/bitArray.ts +++ b/packages/beacon-node/src/util/bitArray.ts @@ -72,7 +72,7 @@ export function intersectUint8Arrays(aUA: Uint8Array, bUA: Uint8Array): Intersec // subset = MUST subset MAYBE equal if (!someExcludes && !someSuperset && someSubset) return IntersectResult.Subset; // intersect = any other condition - else return IntersectResult.Intersect; + return IntersectResult.Intersect; } /** diff --git a/packages/beacon-node/src/util/kzg.ts b/packages/beacon-node/src/util/kzg.ts index e20a379d62ff..42224d1ebaa6 100644 --- a/packages/beacon-node/src/util/kzg.ts +++ b/packages/beacon-node/src/util/kzg.ts @@ -3,8 +3,6 @@ import fs from "node:fs"; import {fileURLToPath} from "node:url"; import {fromHex, toHex} from "@lodestar/utils"; -/* eslint-disable @typescript-eslint/naming-convention */ - // "c-kzg" has hardcoded the mainnet value, do not use params export const FIELD_ELEMENTS_PER_BLOB_MAINNET = 4096; @@ -61,7 +59,7 @@ export enum TrustedFileMode { */ export function loadEthereumTrustedSetup(mode: TrustedFileMode = TrustedFileMode.Txt, filePath?: string): void { try { - let setupFilePath; + let setupFilePath: string; if (mode === TrustedFileMode.Bin) { const binPath = filePath ?? TRUSTED_SETUP_BIN_FILEPATH; const bytes = fs.readFileSync(binPath); @@ -87,9 +85,10 @@ export function loadEthereumTrustedSetup(mode: TrustedFileMode = TrustedFileMode } } -/* eslint-disable @typescript-eslint/naming-convention */ export interface TrustedSetupJSON { + // biome-ignore lint/style/useNamingConvention: Need to be consistent with KZG pattern setup_G1: string[]; + // biome-ignore lint/style/useNamingConvention: Need to be consistent with KZG pattern setup_G2: string[]; } @@ -123,7 +122,9 @@ export function trustedSetupJsonToBin(data: TrustedSetupJSON): TrustedSetupBin { export function trustedSetupBinToJson(bytes: TrustedSetupBin): TrustedSetupJSON { const data: TrustedSetupJSON = { + // biome-ignore lint/style/useNamingConvention: Need to be consistent with KZG pattern setup_G1: [], + // biome-ignore lint/style/useNamingConvention: Need to be consistent with KZG pattern setup_G2: [], }; @@ -157,7 +158,6 @@ export function trustedSetupJsonToTxt(data: TrustedSetupJSON): TrustedSetupTXT { function strip0xPrefix(hex: string): string { if (hex.startsWith("0x")) { return hex.slice(2); - } else { - return hex; } + return hex; } diff --git a/packages/beacon-node/src/util/map.ts b/packages/beacon-node/src/util/map.ts index 6cc8f37155b1..1b8fc6222032 100644 --- a/packages/beacon-node/src/util/map.ts +++ b/packages/beacon-node/src/util/map.ts @@ -43,7 +43,6 @@ export class OrderedMap { } values(): IterableIterator { - // eslint-disable-next-line @typescript-eslint/no-this-alias const _self = this; return (function* generateValues() { for (const key of _self.keys()) { diff --git a/packages/beacon-node/src/util/profile.ts b/packages/beacon-node/src/util/profile.ts index 7085c329c823..ccb5b098be31 100644 --- a/packages/beacon-node/src/util/profile.ts +++ b/packages/beacon-node/src/util/profile.ts @@ -37,7 +37,7 @@ export async function profileNodeJS(durationMs: number): Promise { export async function writeHeapSnapshot(prefix: string, dirpath: string): Promise { // Lazily import NodeJS only modules const fs = await import("node:fs"); - const v8 = await import("v8"); + const v8 = await import("node:v8"); const snapshotStream = v8.getHeapSnapshot(); const filepath = `${dirpath}/${prefix}_${new Date().toISOString()}.heapsnapshot`; const fileStream = fs.createWriteStream(filepath); diff --git a/packages/beacon-node/src/util/queue/errors.ts b/packages/beacon-node/src/util/queue/errors.ts index edb0b3b87374..7117b5629372 100644 --- a/packages/beacon-node/src/util/queue/errors.ts +++ b/packages/beacon-node/src/util/queue/errors.ts @@ -7,11 +7,7 @@ export enum QueueErrorCode { export type QueueErrorCodeType = {code: QueueErrorCode.QUEUE_ABORTED} | {code: QueueErrorCode.QUEUE_MAX_LENGTH}; -export class QueueError extends LodestarError { - constructor(type: QueueErrorCodeType) { - super(type); - } -} +export class QueueError extends LodestarError {} export function isQueueErrorAborted(e: unknown): e is QueueError { return e instanceof QueueError && e.type.code === QueueErrorCode.QUEUE_ABORTED; diff --git a/packages/beacon-node/src/util/queue/fnQueue.ts b/packages/beacon-node/src/util/queue/fnQueue.ts index ec3267bdf93a..80e179f8e8f3 100644 --- a/packages/beacon-node/src/util/queue/fnQueue.ts +++ b/packages/beacon-node/src/util/queue/fnQueue.ts @@ -1,10 +1,10 @@ import {JobItemQueue} from "./itemQueue.js"; import {QueueMetrics, JobQueueOpts} from "./options.js"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: type Fn = (...args: any) => Promise; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export class JobFnQueue extends JobItemQueue<[Fn], any> { constructor(opts: JobQueueOpts, metrics?: QueueMetrics) { super((fn) => fn(), opts, metrics); diff --git a/packages/beacon-node/src/util/queue/itemQueue.ts b/packages/beacon-node/src/util/queue/itemQueue.ts index 7da7b6cdd8cb..f380a15f5eaf 100644 --- a/packages/beacon-node/src/util/queue/itemQueue.ts +++ b/packages/beacon-node/src/util/queue/itemQueue.ts @@ -7,7 +7,8 @@ import {defaultQueueOpts, QueueMetrics, JobQueueOpts, QueueType} from "./options * JobQueue that stores arguments in the job array instead of closures. * Supports a single itemProcessor, for arbitrary functions use the JobFnQueue */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any + +// biome-ignore lint/suspicious/noExplicitAny: export class JobItemQueue { private readonly opts: Required; /** 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 ee0b17348099..b8939b54c294 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 @@ -14,7 +14,7 @@ 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 () { +describe("beacon block api", () => { vi.setConfig({testTimeout: 60_000, hookTimeout: 60_000}); const restPort = 9596; 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 2d2a0a37c59e..6dc01870a844 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 @@ -9,7 +9,7 @@ import {getDevBeaconNode} from "../../../../../utils/node/beacon.js"; import {BeaconNode} from "../../../../../../src/node/nodejs.js"; import {getAndInitDevValidators} from "../../../../../utils/node/validator.js"; -describe("beacon node api", function () { +describe("beacon node api", () => { vi.setConfig({testTimeout: 60_000}); const restPort = 9596; @@ -67,9 +67,7 @@ describe("beacon node api", function () { const bnElOffline = await getDevBeaconNode({ params: { ...chainConfigDef, - // eslint-disable-next-line @typescript-eslint/naming-convention ALTAIR_FORK_EPOCH: 0, - // eslint-disable-next-line @typescript-eslint/naming-convention BELLATRIX_FORK_EPOCH: 0, }, options: { 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 01423d72341f..d8c87a3ba6b8 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 @@ -8,7 +8,7 @@ import {LogLevel, testLogger} from "../../../../../utils/logger.js"; import {getDevBeaconNode} from "../../../../../utils/node/beacon.js"; import {BeaconNode} from "../../../../../../src/node/nodejs.js"; -describe("beacon state api", function () { +describe("beacon state api", () => { const restPort = 9596; const config = createBeaconConfig(chainConfigDef, Buffer.alloc(32, 0xaa)); const validatorCount = 512; diff --git a/packages/beacon-node/test/e2e/api/impl/config.test.ts b/packages/beacon-node/test/e2e/api/impl/config.test.ts index 4bdedfa16391..3ffdbc4beb21 100644 --- a/packages/beacon-node/test/e2e/api/impl/config.test.ts +++ b/packages/beacon-node/test/e2e/api/impl/config.test.ts @@ -14,7 +14,7 @@ const CONSTANT_NAMES_SKIP_LIST = new Set([ "BLOB_SIDECAR_SUBNET_COUNT", ]); -describe("api / impl / config", function () { +describe("api / impl / config", () => { it("Ensure all constants are exposed", async () => { const constantNames = await downloadRemoteConstants(ethereumConsensusSpecsTests.specVersion); 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 81154005af68..effbed19fc2c 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 @@ -13,8 +13,7 @@ import {getAndInitDevValidators} from "../../../../utils/node/validator.js"; import {BeaconNode} from "../../../../../src/node/nodejs.js"; import {waitForEvent} from "../../../../utils/events/resolver.js"; -/* eslint-disable @typescript-eslint/naming-convention */ -describe("lightclient api", function () { +describe("lightclient api", () => { const SECONDS_PER_SLOT = 1; const ALTAIR_FORK_EPOCH = 0; const restPort = 9596; @@ -81,7 +80,7 @@ describe("lightclient api", function () { await sleep(2 * SECONDS_PER_SLOT * 1000); }; - it("getLightClientUpdatesByRange()", async function () { + it("getLightClientUpdatesByRange()", async () => { const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).lightclient; await waitForBestUpdate(); const res = await client.getLightClientUpdatesByRange({startPeriod: 0, count: 1}); @@ -92,7 +91,7 @@ describe("lightclient api", function () { expect(res.meta().versions[0]).toBe(ForkName.altair); }); - it("getLightClientOptimisticUpdate()", async function () { + it("getLightClientOptimisticUpdate()", async () => { await waitForBestUpdate(); const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).lightclient; const res = await client.getLightClientOptimisticUpdate(); @@ -106,7 +105,7 @@ describe("lightclient api", function () { expect(res.headers.get(HttpHeader.ExposeHeaders)?.includes("Eth-Consensus-Version")).toBe(true); }); - it.skip("getLightClientFinalityUpdate()", async function () { + it.skip("getLightClientFinalityUpdate()", async () => { // 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); @@ -115,14 +114,14 @@ describe("lightclient api", function () { expect(finalityUpdate).toBeDefined(); }); - it("getLightClientCommitteeRoot() for the 1st period", async function () { + it("getLightClientCommitteeRoot() for the 1st period", async () => { await waitForBestUpdate(); const lightclient = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).lightclient; const committeeRes = await lightclient.getLightClientCommitteeRoot({startPeriod: 0, count: 1}); committeeRes.assertOk(); const client = getClient({baseUrl: `http://127.0.0.1:${restPort}`}, {config}).beacon; - const validators = (await client.getStateValidators({stateId: "head"})).value(); + const validators = (await client.postStateValidators({stateId: "head"})).value(); const pubkeys = validators.map((v) => v.validator.pubkey); expect(pubkeys.length).toBe(validatorCount); // only 2 validators spreading to 512 committee slots 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 5cccb2aa0772..bc847b47e9a9 100644 --- a/packages/beacon-node/test/e2e/api/lodestar/lodestar.test.ts +++ b/packages/beacon-node/test/e2e/api/lodestar/lodestar.test.ts @@ -10,17 +10,16 @@ import {waitForEvent} from "../../../utils/events/resolver.js"; import {ClockEvent} from "../../../../src/util/clock.js"; import {BeaconNode} from "../../../../src/index.js"; -describe("api / impl / validator", function () { +describe("api / impl / validator", () => { vi.setConfig({testTimeout: 60_000}); - describe("getLiveness endpoint", function () { + describe("getLiveness endpoint", () => { let bn: BeaconNode | undefined; const SECONDS_PER_SLOT = 2; const ALTAIR_FORK_EPOCH = 0; const validatorCount = 8; const restPort = 9596; const testParams: Pick = { - /* eslint-disable @typescript-eslint/naming-convention */ SECONDS_PER_SLOT: SECONDS_PER_SLOT, ALTAIR_FORK_EPOCH: ALTAIR_FORK_EPOCH, }; @@ -31,7 +30,7 @@ describe("api / impl / validator", function () { if (bn) await bn.close(); }); - it("Should return validator indices that are live", async function () { + it("Should return validator indices that are live", async () => { const chainConfig: ChainConfig = {...chainConfigDef, SECONDS_PER_SLOT, ALTAIR_FORK_EPOCH}; const genesisValidatorsRoot = Buffer.alloc(32, 0xaa); const config = createBeaconConfig(chainConfig, genesisValidatorsRoot); @@ -72,7 +71,7 @@ describe("api / impl / validator", function () { ]); }); - it("Should return only for previous, current and next epoch", async function () { + it("Should return only for previous, current and next epoch", async () => { const chainConfig: ChainConfig = {...chainConfigDef, SECONDS_PER_SLOT, ALTAIR_FORK_EPOCH}; const genesisValidatorsRoot = Buffer.alloc(32, 0xaa); const config = createBeaconConfig(chainConfig, genesisValidatorsRoot); diff --git a/packages/beacon-node/test/e2e/chain/bls/multithread.test.ts b/packages/beacon-node/test/e2e/chain/bls/multithread.test.ts index 25f0d5133bcd..a544b3231e87 100644 --- a/packages/beacon-node/test/e2e/chain/bls/multithread.test.ts +++ b/packages/beacon-node/test/e2e/chain/bls/multithread.test.ts @@ -5,23 +5,11 @@ import {BlsMultiThreadWorkerPool} from "../../../../src/chain/bls/multithread/in import {testLogger} from "../../../utils/logger.js"; import {VerifySignatureOpts} from "../../../../src/chain/bls/interface.js"; -describe("chain / bls / multithread queue", function () { +describe("chain / bls / multithread queue", () => { const logger = testLogger(); let controller: AbortController; - beforeEach(() => { - controller = new AbortController(); - }); - afterEach(() => controller.abort()); - const afterEachCallbacks: (() => Promise | void)[] = []; - afterEach(async () => { - while (afterEachCallbacks.length > 0) { - const callback = afterEachCallbacks.pop(); - if (callback) await callback(); - } - }); - const sets: ISignatureSet[] = []; const sameMessageSets: {publicKey: PublicKey; signature: Uint8Array}[] = []; const sameMessage = Buffer.alloc(32, 100); @@ -45,6 +33,19 @@ describe("chain / bls / multithread queue", function () { } }); + beforeEach(() => { + controller = new AbortController(); + }); + + afterEach(async () => { + controller.abort(); + + while (afterEachCallbacks.length > 0) { + const callback = afterEachCallbacks.pop(); + if (callback) await callback(); + } + }); + async function initializePool(): Promise { const pool = new BlsMultiThreadWorkerPool({}, {logger, metrics: null}); // await terminating all workers diff --git a/packages/beacon-node/test/e2e/chain/lightclient.test.ts b/packages/beacon-node/test/e2e/chain/lightclient.test.ts index c501a2618049..ed698f2844a1 100644 --- a/packages/beacon-node/test/e2e/chain/lightclient.test.ts +++ b/packages/beacon-node/test/e2e/chain/lightclient.test.ts @@ -14,7 +14,7 @@ import {getDevBeaconNode} from "../../utils/node/beacon.js"; import {getAndInitDevValidators} from "../../utils/node/validator.js"; import {HeadEventData} from "../../../src/chain/index.js"; -describe("chain / lightclient", function () { +describe("chain / lightclient", () => { vi.setConfig({testTimeout: 600_000}); /** @@ -34,7 +34,6 @@ describe("chain / lightclient", function () { const restPort = 9000; const testParams: Pick = { - /* eslint-disable @typescript-eslint/naming-convention */ SECONDS_PER_SLOT: 1, ALTAIR_FORK_EPOCH: 0, }; @@ -47,7 +46,7 @@ describe("chain / lightclient", function () { } }); - it("Lightclient track head on server configuration", async function () { + it("Lightclient track head on server configuration", async () => { // delay a bit so regular sync sees it's up to date and sync is completed from the beginning // also delay to allow bls workers to be transpiled/initialized const genesisSlotsDelay = 7; @@ -153,7 +152,9 @@ describe("chain / lightclient", function () { const lcHeadSlot = lightclient.getHead().beacon.slot; if (head.slot - lcHeadSlot > maxLcHeadTrackingDiffSlots) { throw Error(`Lightclient head ${lcHeadSlot} is too far behind the beacon node ${head.slot}`); - } else if (head.slot > targetSlotToReach) { + } + + if (head.slot > targetSlotToReach) { resolve(); } } catch (e) { diff --git a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts index 594e20ec4cb0..fd6af72ad6cb 100644 --- a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts +++ b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts @@ -12,18 +12,15 @@ 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 () { +describe("proposer boost reorg", () => { vi.setConfig({testTimeout: 60000}); 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, }; 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 005b28baeefc..fcac715e1719 100644 --- a/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts +++ b/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts @@ -19,12 +19,11 @@ import {ReorgedForkChoice} from "../../../mocks/fork-choice/reorg.js"; * This includes several tests which make >6 min to pass in CI, so let's only run 1 of them and leave remaining ones * for local investigation. */ -describe("regen/reload states with n-historical states configuration", function () { +describe("regen/reload states with n-historical states configuration", () => { vi.setConfig({testTimeout: 96_000}); const validatorCount = 8; const testParams: Pick = { - // eslint-disable-next-line @typescript-eslint/naming-convention SECONDS_PER_SLOT: 2, }; @@ -267,7 +266,7 @@ describe("regen/reload states with n-historical states configuration", function skip, } of testCases) { const wrappedIt = skip ? it.skip : it; - wrappedIt(`${name} reorgedSlot=${reorgedSlot} reorgDistance=${reorgDistance}`, async function () { + wrappedIt(`${name} reorgedSlot=${reorgedSlot} reorgDistance=${reorgDistance}`, 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; @@ -412,11 +411,10 @@ describe("regen/reload states with n-historical states configuration", function )?.value ).toEqual(reloadCount); - const stateSszMetricValues = await (followupBn.metrics?.cpStateCache.stateSerializeDuration as Histogram).get(); + const stateSszMetricValues = await (followupBn.metrics?.stateSerializeDuration as Histogram).get(); expect( - stateSszMetricValues?.values.find( - (value) => value.metricName === "lodestar_cp_state_cache_state_serialize_seconds_count" - )?.value + stateSszMetricValues?.values.find((value) => value.metricName === "lodestar_state_serialize_seconds_count") + ?.value ).toEqual(persistCount); // assert number of persisted/in-memory states diff --git a/packages/beacon-node/test/e2e/db/api/beacon/repositories/blockArchive.test.ts b/packages/beacon-node/test/e2e/db/api/beacon/repositories/blockArchive.test.ts index 5ecdc4c8b963..3c7a37e52a8b 100644 --- a/packages/beacon-node/test/e2e/db/api/beacon/repositories/blockArchive.test.ts +++ b/packages/beacon-node/test/e2e/db/api/beacon/repositories/blockArchive.test.ts @@ -4,7 +4,7 @@ import {ssz} from "@lodestar/types"; import {BeaconDb} from "../../../../../../src/db/index.js"; import {startTmpBeaconDb} from "../../../../../utils/db.js"; -describe("BlockArchiveRepository", function () { +describe("BlockArchiveRepository", () => { let db: BeaconDb; const sampleBlock = ssz.phase0.SignedBeaconBlock.defaultValue(); diff --git a/packages/beacon-node/test/e2e/doppelganger/doppelganger.test.ts b/packages/beacon-node/test/e2e/doppelganger/doppelganger.test.ts index ab9061f1eacd..949d0104d641 100644 --- a/packages/beacon-node/test/e2e/doppelganger/doppelganger.test.ts +++ b/packages/beacon-node/test/e2e/doppelganger/doppelganger.test.ts @@ -22,7 +22,7 @@ import {BeaconNode} from "../../../src/node/index.js"; // Attempting to do both 1. and 2. in this e2e test more expensive than necessary. // Unit tests in the validator cover 2., so some test in lodestar package should cover 1. // https://github.com/ChainSafe/lodestar/issues/5967 -describe.skip("doppelganger / doppelganger test", function () { +describe.skip("doppelganger / doppelganger test", () => { const afterEachCallbacks: (() => Promise | void)[] = []; afterEach(async () => { while (afterEachCallbacks.length > 0) { @@ -34,7 +34,6 @@ describe.skip("doppelganger / doppelganger test", function () { const validatorCount = 1; const genesisSlotsDelay = 5; const beaconParams: Pick = { - // eslint-disable-next-line @typescript-eslint/naming-convention SECONDS_PER_SLOT: 2, }; @@ -78,7 +77,7 @@ describe.skip("doppelganger / doppelganger test", function () { return {beaconNode: bn, validators: validatorsWithDoppelganger}; } - it("should not have doppelganger protection if started before genesis", async function () { + it("should not have doppelganger protection if started before genesis", async () => { const committeeIndex = 0; const validatorIndex = 0; @@ -114,7 +113,7 @@ describe.skip("doppelganger / doppelganger test", function () { ); }); - it("should shut down validator if same key is active and started after genesis", async function () { + it("should shut down validator if same key is active and started after genesis", async () => { // set genesis time to allow at least an epoch const genesisTime = Math.floor(Date.now() / 1000) - SLOTS_PER_EPOCH * beaconParams.SECONDS_PER_SLOT; @@ -123,7 +122,7 @@ describe.skip("doppelganger / doppelganger test", function () { doppelgangerProtection: true, }); - const {beaconNode: bn2, validators: validators} = await createBNAndVC({ + const {beaconNode: bn2, validators} = await createBNAndVC({ genesisTime: bn.chain.getHeadState().genesisTime, }); @@ -150,7 +149,7 @@ describe.skip("doppelganger / doppelganger test", function () { ); }); - it("should shut down validator if same key is active with same BN and started after genesis", async function () { + it("should shut down validator if same key is active with same BN and started after genesis", async () => { const doppelgangerProtection = true; const testLoggerOpts: TestLoggerOpts = {level: LogLevel.info}; @@ -195,14 +194,14 @@ describe.skip("doppelganger / doppelganger test", function () { ); }); - it("should not shut down validator if key is different", async function () { + it("should not shut down validator if key is different", async () => { const doppelgangerProtection = true; const {beaconNode: bn, validators: validatorsWithDoppelganger} = await createBNAndVC({ doppelgangerProtection, }); - const {beaconNode: bn2, validators: validators} = await createBNAndVC({ + const {beaconNode: bn2, validators} = await createBNAndVC({ genesisTime: bn.chain.getHeadState().genesisTime, doppelgangerProtection: false, }); @@ -228,7 +227,7 @@ describe.skip("doppelganger / doppelganger test", function () { ); }); - it("should not sign block if doppelganger period has not passed and not started at genesis", async function () { + it("should not sign block if doppelganger period has not passed and not started at genesis", async () => { const doppelgangerProtection = true; // set genesis time to allow at least an epoch @@ -259,7 +258,7 @@ describe.skip("doppelganger / doppelganger test", function () { ).resolves.toBeUndefined(); }); - it("should not sign attestations if doppelganger period has not passed and started after genesis", async function () { + it("should not sign attestations if doppelganger period has not passed and started after genesis", async () => { const doppelgangerProtection = true; // set genesis time to allow at least an epoch diff --git a/packages/beacon-node/test/e2e/eth1/eth1ForBlockProduction.test.ts b/packages/beacon-node/test/e2e/eth1/eth1ForBlockProduction.test.ts index 17692cfcca3a..21cadeb55d2c 100644 --- a/packages/beacon-node/test/e2e/eth1/eth1ForBlockProduction.test.ts +++ b/packages/beacon-node/test/e2e/eth1/eth1ForBlockProduction.test.ts @@ -25,7 +25,7 @@ const pyrmontDepositsDataRoot = [ ]; // https://github.com/ChainSafe/lodestar/issues/5967 -describe.skip("eth1 / Eth1Provider", function () { +describe.skip("eth1 / Eth1Provider", () => { const controller = new AbortController(); const config = getTestnetConfig(); @@ -48,7 +48,7 @@ describe.skip("eth1 / Eth1Provider", function () { await LevelDbController.destroy(dbLocation); }); - it("Should fetch real Pyrmont eth1 data for block proposing", async function () { + it("Should fetch real Pyrmont eth1 data for block proposing", async () => { const eth1Options: Eth1Options = { enabled: true, providerUrls: [getGoerliRpcUrl()], @@ -68,7 +68,6 @@ describe.skip("eth1 / Eth1Provider", function () { // Resolves when Eth1ForBlockProduction has fetched both blocks and deposits const {eth1Datas, deposits} = await (async function resolveWithEth1DataAndDeposits() { - // eslint-disable-next-line no-constant-condition while (true) { const eth1Datas = await db.eth1Data.entries(); const deposits = await db.depositEvent.values(); diff --git a/packages/beacon-node/test/e2e/eth1/eth1MergeBlockTracker.test.ts b/packages/beacon-node/test/e2e/eth1/eth1MergeBlockTracker.test.ts index ae3e0c707542..9423bb716b3f 100644 --- a/packages/beacon-node/test/e2e/eth1/eth1MergeBlockTracker.test.ts +++ b/packages/beacon-node/test/e2e/eth1/eth1MergeBlockTracker.test.ts @@ -10,13 +10,11 @@ import {quantityToBigint} from "../../../src/eth1/provider/utils.js"; import {ZERO_HASH} from "../../../src/constants/index.js"; import {getGoerliRpcUrl} from "../../testParams.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - // This test is constantly failing. We must unblock PR so this issue is a TODO to debug it and re-enable latter. // It's OKAY to disable temporarily since this functionality is tested indirectly by the sim merge tests. // See https://github.com/ChainSafe/lodestar/issues/4197 // https://github.com/ChainSafe/lodestar/issues/5967 -describe.skip("eth1 / Eth1MergeBlockTracker", function () { +describe.skip("eth1 / Eth1MergeBlockTracker", () => { const logger = testLogger(); function getConfig(ttd: bigint): ChainConfig { diff --git a/packages/beacon-node/test/e2e/eth1/eth1Provider.test.ts b/packages/beacon-node/test/e2e/eth1/eth1Provider.test.ts index 8b7e9503485e..606845f40337 100644 --- a/packages/beacon-node/test/e2e/eth1/eth1Provider.test.ts +++ b/packages/beacon-node/test/e2e/eth1/eth1Provider.test.ts @@ -8,7 +8,7 @@ import {Eth1Block} from "../../../src/eth1/interface.js"; import {getGoerliRpcUrl} from "../../testParams.js"; // https://github.com/ChainSafe/lodestar/issues/5967 -describe.skip("eth1 / Eth1Provider", function () { +describe.skip("eth1 / Eth1Provider", () => { let controller: AbortController; beforeEach(() => { controller = new AbortController(); @@ -28,16 +28,16 @@ describe.skip("eth1 / Eth1Provider", function () { return new Eth1Provider(config, eth1Options, controller.signal); } - it("Should validate contract", async function () { + it("Should validate contract", async () => { await getEth1Provider().validateContract(); }); - it("Should get latest block number", async function () { + it("Should get latest block number", async () => { const blockNumber = await getEth1Provider().getBlockNumber(); expect(blockNumber).toBeGreaterThan(0); }); - it("Should get a specific block by number", async function () { + it("Should get a specific block by number", async () => { const goerliGenesisBlock: Eth1Block = { blockHash: fromHexString("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"), blockNumber: 0, @@ -47,7 +47,7 @@ describe.skip("eth1 / Eth1Provider", function () { expect(block && parseEth1Block(block)).toEqual(goerliGenesisBlock); }); - it("Should get deposits events for a block range", async function () { + it("Should get deposits events for a block range", async () => { const blockNumbers = goerliTestnetDepositEvents.map((log) => log.blockNumber); const fromBlock = Math.min(...blockNumbers); const toBlock = Math.min(...blockNumbers); @@ -74,26 +74,26 @@ describe.skip("eth1 / Eth1Provider", function () { code: "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a", }; - it("getBlocksByNumber: Should fetch a block range", async function () { + it("getBlocksByNumber: Should fetch a block range", async () => { const fromBlock = firstGoerliBlocks[0].blockNumber; const toBlock = firstGoerliBlocks[firstGoerliBlocks.length - 1].blockNumber; const blocks = await getEth1Provider().getBlocksByNumber(fromBlock, toBlock); expect(blocks.map(parseEth1Block)).toEqual(firstGoerliBlocks); }); - it("getBlockByNumber: Should fetch a single block", async function () { + it("getBlockByNumber: Should fetch a single block", async () => { const firstGoerliBlock = firstGoerliBlocks[0]; const block = await getEth1Provider().getBlockByNumber(firstGoerliBlock.blockNumber); expect(block && parseEth1Block(block)).toEqual(firstGoerliBlock); }); - it("getBlockNumber: Should fetch latest block number", async function () { + it("getBlockNumber: Should fetch latest block number", async () => { const blockNumber = await getEth1Provider().getBlockNumber(); expect(blockNumber).toBeInstanceOf(Number); expect(blockNumber).toBeGreaterThan(0); }); - it("getCode: Should fetch code for a contract", async function () { + it("getCode: Should fetch code for a contract", async () => { const code = await getEth1Provider().getCode(goerliSampleContract.address); expect(code).toEqual(expect.arrayContaining([goerliSampleContract.code])); }); diff --git a/packages/beacon-node/test/e2e/eth1/jsonRpcHttpClient.test.ts b/packages/beacon-node/test/e2e/eth1/jsonRpcHttpClient.test.ts index 4e290c6d6318..91131a89f379 100644 --- a/packages/beacon-node/test/e2e/eth1/jsonRpcHttpClient.test.ts +++ b/packages/beacon-node/test/e2e/eth1/jsonRpcHttpClient.test.ts @@ -7,7 +7,7 @@ import {JsonRpcHttpClient} from "../../../src/eth1/provider/jsonRpcHttpClient.js import {getGoerliRpcUrl} from "../../testParams.js"; import {RpcPayload} from "../../../src/eth1/interface.js"; -describe("eth1 / jsonRpcHttpClient", function () { +describe("eth1 / jsonRpcHttpClient", () => { vi.setConfig({testTimeout: 10_000}); const port = 36421; @@ -41,13 +41,13 @@ describe("eth1 / jsonRpcHttpClient", function () { { id: "Bad port", url: `http://localhost:${port + 1}`, - requestListener: (req, res) => res.end(), + requestListener: (_req, res) => res.end(), error: "", errorCode: "ECONNREFUSED", }, { id: "Not a JSON RPC endpoint", - requestListener: (req, res) => { + requestListener: (_req, res) => { res.setHeader("Content-Type", "text/html"); res.end(""); }, @@ -55,7 +55,7 @@ describe("eth1 / jsonRpcHttpClient", function () { }, { id: "Endpoint returns HTTP error", - requestListener: (req, res) => { + requestListener: (_req, res) => { res.statusCode = 404; res.end(); }, @@ -63,7 +63,7 @@ describe("eth1 / jsonRpcHttpClient", function () { }, { id: "RPC payload with error", - requestListener: (req, res) => { + requestListener: (_req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify({jsonrpc: "2.0", id: 83, error: noMethodError})); }, @@ -71,7 +71,7 @@ describe("eth1 / jsonRpcHttpClient", function () { }, { id: "RPC payload with non-spec error: error has no message", - requestListener: (req, res) => { + requestListener: (_req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify({jsonrpc: "2.0", id: 83, error: {code: noMethodError.code}})); }, @@ -79,7 +79,7 @@ describe("eth1 / jsonRpcHttpClient", function () { }, { id: "RPC payload with non-spec error: error is a string", - requestListener: (req, res) => { + requestListener: (_req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify({jsonrpc: "2.0", id: 83, error: notInSpecError})); }, @@ -87,7 +87,7 @@ describe("eth1 / jsonRpcHttpClient", function () { }, { id: "RPC payload with no result", - requestListener: (req, res) => { + requestListener: (_req, res) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify({jsonrpc: "2.0", id: 83})); }, @@ -113,7 +113,7 @@ describe("eth1 / jsonRpcHttpClient", function () { const afterHooks: (() => Promise)[] = []; - afterEach(async function () { + afterEach(async () => { while (afterHooks.length) { const afterHook = afterHooks.pop(); if (afterHook) await afterHook(); @@ -124,7 +124,7 @@ describe("eth1 / jsonRpcHttpClient", function () { const {id, requestListener, abort, timeout} = testCase; let {url, payload} = testCase; - it(id, async function () { + it(id, async () => { if (requestListener) { if (!url) url = `http://localhost:${port}`; @@ -162,25 +162,24 @@ describe("eth1 / jsonRpcHttpClient", function () { } }); -describe("eth1 / jsonRpcHttpClient - with retries", function () { +describe("eth1 / jsonRpcHttpClient - with retries", () => { vi.setConfig({testTimeout: 10_000}); const port = 36421; const noMethodError = {code: -32601, message: "Method not found"}; const afterHooks: (() => Promise)[] = []; - afterEach(async function () { + afterEach(async () => { while (afterHooks.length) { const afterHook = afterHooks.pop(); if (afterHook) await afterHook().catch((e: Error) => { - // eslint-disable-next-line no-console console.error("Error in afterEach hook", e); }); } }); - it("should retry ENOTFOUND", async function () { + it("should retry ENOTFOUND", async () => { let retryCount = 0; const url = "https://goerli.fake-website.io"; @@ -202,7 +201,7 @@ describe("eth1 / jsonRpcHttpClient - with retries", function () { expect(retryCount).toBeWithMessage(retries, "ENOTFOUND should be retried before failing"); }); - it("should retry ECONNREFUSED", async function () { + it("should retry ECONNREFUSED", async () => { let retryCount = 0; const url = `http://localhost:${port + 1}`; @@ -224,10 +223,10 @@ describe("eth1 / jsonRpcHttpClient - with retries", function () { expect(retryCount).toBeWithMessage(retries, "code ECONNREFUSED should be retried before failing"); }); - it("should retry 404", async function () { + it("should retry 404", async () => { let requestCount = 0; - const server = http.createServer((req, res) => { + const server = http.createServer((_req, res) => { requestCount++; res.statusCode = 404; res.end(); @@ -254,7 +253,7 @@ describe("eth1 / jsonRpcHttpClient - with retries", function () { expect(requestCount).toBeWithMessage(retries + 1, "404 responses should be retried before failing"); }); - it("should retry timeout", async function () { + it("should retry timeout", async () => { let requestCount = 0; const server = http.createServer(async () => { @@ -285,7 +284,7 @@ describe("eth1 / jsonRpcHttpClient - with retries", function () { expect(requestCount).toBeWithMessage(retries + 1, "Timeout request should be retried before failing"); }); - it("should not retry aborted", async function () { + it("should not retry aborted", async () => { let requestCount = 0; const server = http.createServer(() => { requestCount++; @@ -315,10 +314,10 @@ describe("eth1 / jsonRpcHttpClient - with retries", function () { expect(requestCount).toBeWithMessage(1, "Aborted request should not be retried"); }); - it("should not retry payload error", async function () { + it("should not retry payload error", async () => { let requestCount = 0; - const server = http.createServer((req, res) => { + const server = http.createServer((_req, res) => { requestCount++; res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify({jsonrpc: "2.0", id: 83, error: noMethodError})); diff --git a/packages/beacon-node/test/e2e/eth1/stream.test.ts b/packages/beacon-node/test/e2e/eth1/stream.test.ts index a683e885b453..ce65d4353f0e 100644 --- a/packages/beacon-node/test/e2e/eth1/stream.test.ts +++ b/packages/beacon-node/test/e2e/eth1/stream.test.ts @@ -6,7 +6,7 @@ import {getGoerliRpcUrl} from "../../testParams.js"; import {Eth1Options} from "../../../src/eth1/options.js"; // https://github.com/ChainSafe/lodestar/issues/5967 -describe.skip("Eth1 streams", function () { +describe.skip("Eth1 streams", () => { let controller: AbortController; beforeEach(() => { controller = new AbortController(); @@ -30,7 +30,7 @@ describe.skip("Eth1 streams", function () { const depositsToFetch = 1000; const eth1Params = {...config, maxBlocksPerPoll}; - it(`Should fetch ${depositsToFetch} deposits with getDepositsStream`, async function () { + it(`Should fetch ${depositsToFetch} deposits with getDepositsStream`, async () => { const depositsStream = getDepositsStream( medallaTestnetConfig.blockWithDepositActivity, getEth1Provider(), @@ -49,7 +49,7 @@ describe.skip("Eth1 streams", function () { expect(depositCount).toBeGreaterThan(depositsToFetch); }); - it(`Should fetch ${depositsToFetch} deposits with getDepositsAndBlockStreamForGenesis`, async function () { + it(`Should fetch ${depositsToFetch} deposits with getDepositsAndBlockStreamForGenesis`, async () => { const stream = getDepositsAndBlockStreamForGenesis( medallaTestnetConfig.blockWithDepositActivity, getEth1Provider(), diff --git a/packages/beacon-node/test/e2e/interop/genesisState.test.ts b/packages/beacon-node/test/e2e/interop/genesisState.test.ts index 2287c6a1deb8..739319a4257c 100644 --- a/packages/beacon-node/test/e2e/interop/genesisState.test.ts +++ b/packages/beacon-node/test/e2e/interop/genesisState.test.ts @@ -9,7 +9,6 @@ describe("interop / initDevState", () => { it("Create interop deposits", () => { const deposits = interopDeposits(config, ssz.phase0.DepositDataRootList.defaultViewDU(), 1); - /* eslint-disable @typescript-eslint/naming-convention */ expect(deposits.map((deposit) => ssz.phase0.Deposit.toJson(deposit))).toEqual([ { proof: [ diff --git a/packages/beacon-node/test/e2e/network/gossipsub.test.ts b/packages/beacon-node/test/e2e/network/gossipsub.test.ts index c19f3c7c4388..29a745455561 100644 --- a/packages/beacon-node/test/e2e/network/gossipsub.test.ts +++ b/packages/beacon-node/test/e2e/network/gossipsub.test.ts @@ -8,7 +8,7 @@ import {GossipType, GossipHandlers, GossipHandlerParamGeneric} from "../../../sr import {getNetworkForTest} from "../../utils/networkWithMockDb.js"; import {connect, onPeerConnect} from "../../utils/network.js"; -describe("gossipsub / main thread", function () { +describe("gossipsub / main thread", () => { vi.setConfig({testTimeout: 3000}); runTests({useWorker: false}); @@ -19,7 +19,7 @@ describe("gossipsub / main thread", function () { * Since we use vitest to run tests in parallel, including this causes the test to be unstable. * See https://github.com/ChainSafe/lodestar/issues/6358 */ -describe.skip("gossipsub / worker", function () { +describe.skip("gossipsub / worker", () => { vi.setConfig({testTimeout: 3000}); runTests({useWorker: true}); @@ -35,7 +35,6 @@ function runTests({useWorker}: {useWorker: boolean}): void { }); // Schedule all forks at ALTAIR_FORK_EPOCH to avoid generating the pubkeys cache - /* eslint-disable @typescript-eslint/naming-convention */ const config = createChainForkConfig({ ...defaultChainConfig, ALTAIR_FORK_EPOCH: 1, @@ -44,7 +43,6 @@ function runTests({useWorker}: {useWorker: boolean}): void { }); const START_SLOT = computeStartSlotAtEpoch(config.ALTAIR_FORK_EPOCH); - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function mockModules(gossipHandlersPartial?: Partial) { const [netA, closeA] = await getNetworkForTest(`gossipsub-${useWorker ? "worker" : "main"}-A`, config, { opts: {useWorker}, @@ -63,9 +61,11 @@ function runTests({useWorker}: {useWorker: boolean}): void { return {netA, netB}; } - it("Publish and receive a voluntaryExit", async function () { + it("Publish and receive a voluntaryExit", async () => { let onVoluntaryExit: (ve: Uint8Array) => void; - const onVoluntaryExitPromise = new Promise((resolve) => (onVoluntaryExit = resolve)); + const onVoluntaryExitPromise = new Promise((resolve) => { + onVoluntaryExit = resolve; + }); const {netA, netB} = await mockModules({ [GossipType.voluntary_exit]: async ({gossipData}: GossipHandlerParamGeneric) => { @@ -98,9 +98,11 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("Publish and receive a blsToExecutionChange", async function () { + it("Publish and receive a blsToExecutionChange", async () => { let onBlsToExecutionChange: (blsToExec: Uint8Array) => void; - const onBlsToExecutionChangePromise = new Promise((resolve) => (onBlsToExecutionChange = resolve)); + const onBlsToExecutionChangePromise = new Promise((resolve) => { + onBlsToExecutionChange = resolve; + }); const {netA, netB} = await mockModules({ [GossipType.bls_to_execution_change]: async ({ @@ -134,9 +136,11 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("Publish and receive an attesterSlashing", async function () { + it("Publish and receive an attesterSlashing", async () => { let onAttesterSlashingChange: (payload: Uint8Array) => void; - const onAttesterSlashingChangePromise = new Promise((resolve) => (onAttesterSlashingChange = resolve)); + const onAttesterSlashingChangePromise = new Promise((resolve) => { + onAttesterSlashingChange = resolve; + }); const {netA, netB} = await mockModules({ [GossipType.attester_slashing]: async ({gossipData}: GossipHandlerParamGeneric) => { @@ -166,9 +170,11 @@ function runTests({useWorker}: {useWorker: boolean}): void { expect(Buffer.from(received)).toEqual(Buffer.from(ssz.phase0.AttesterSlashing.serialize(attesterSlashing))); }); - it("Publish and receive a proposerSlashing", async function () { + it("Publish and receive a proposerSlashing", async () => { let onProposerSlashingChange: (payload: Uint8Array) => void; - const onProposerSlashingChangePromise = new Promise((resolve) => (onProposerSlashingChange = resolve)); + const onProposerSlashingChangePromise = new Promise((resolve) => { + onProposerSlashingChange = resolve; + }); const {netA, netB} = await mockModules({ [GossipType.proposer_slashing]: async ({gossipData}: GossipHandlerParamGeneric) => { @@ -198,11 +204,11 @@ function runTests({useWorker}: {useWorker: boolean}): void { expect(Buffer.from(received)).toEqual(Buffer.from(ssz.phase0.ProposerSlashing.serialize(proposerSlashing))); }); - it("Publish and receive a LightClientOptimisticUpdate", async function () { + it("Publish and receive a LightClientOptimisticUpdate", async () => { let onLightClientOptimisticUpdate: (ou: Uint8Array) => void; - const onLightClientOptimisticUpdatePromise = new Promise( - (resolve) => (onLightClientOptimisticUpdate = resolve) - ); + const onLightClientOptimisticUpdatePromise = new Promise((resolve) => { + onLightClientOptimisticUpdate = resolve; + }); const {netA, netB} = await mockModules({ [GossipType.light_client_optimistic_update]: async ({ @@ -237,11 +243,11 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("Publish and receive a LightClientFinalityUpdate", async function () { + it("Publish and receive a LightClientFinalityUpdate", async () => { let onLightClientFinalityUpdate: (fu: Uint8Array) => void; - const onLightClientFinalityUpdatePromise = new Promise( - (resolve) => (onLightClientFinalityUpdate = resolve) - ); + const onLightClientFinalityUpdatePromise = new Promise((resolve) => { + onLightClientFinalityUpdate = resolve; + }); const {netA, netB} = await mockModules({ [GossipType.light_client_finality_update]: async ({ diff --git a/packages/beacon-node/test/e2e/network/mdns.test.ts b/packages/beacon-node/test/e2e/network/mdns.test.ts index d8e148e191fe..f8b55d8afae7 100644 --- a/packages/beacon-node/test/e2e/network/mdns.test.ts +++ b/packages/beacon-node/test/e2e/network/mdns.test.ts @@ -21,18 +21,19 @@ let port = 9000; const mu = "/ip4/127.0.0.1/tcp/0"; // https://github.com/ChainSafe/lodestar/issues/5967 -describe.skip("mdns", function () { +describe.skip("mdns", () => { const afterEachCallbacks: (() => Promise | void)[] = []; - afterEach(async () => { - await Promise.all(afterEachCallbacks.map((cb) => cb())); - afterEachCallbacks.splice(0, afterEachCallbacks.length); - }); - let controller: AbortController; + beforeEach(() => { controller = new AbortController(); }); - afterEach(() => controller.abort()); + + afterEach(async () => { + await Promise.all(afterEachCallbacks.map((cb) => cb())); + afterEachCallbacks.splice(0, afterEachCallbacks.length); + controller.abort(); + }); async function getOpts(peerId: PeerId): Promise { const bindAddrUdp = `/ip4/0.0.0.0/udp/${port++}`; @@ -68,7 +69,6 @@ describe.skip("mdns", function () { return {block, state, config: beaconConfig}; }); - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function createTestNode(nodeName: string) { const {config} = getStaticData(); const chain = getMockedBeaconChain(); @@ -111,12 +111,11 @@ describe.skip("mdns", function () { return {network, chain}; } - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function createTestNodesAB() { return Promise.all([createTestNode("mdns-A"), createTestNode("mdns-B")]); } - it("should connect two peers on a LAN", async function () { + it("should connect two peers on a LAN", async () => { const [{network: netA}, {network: netB}] = await createTestNodesAB(); await Promise.all([onPeerConnect(netA), onPeerConnect(netB)]); expect(netA.getConnectedPeerCount()).toBe(1); diff --git a/packages/beacon-node/test/e2e/network/network.test.ts b/packages/beacon-node/test/e2e/network/network.test.ts index 094a75280e8b..2435b005efa1 100644 --- a/packages/beacon-node/test/e2e/network/network.test.ts +++ b/packages/beacon-node/test/e2e/network/network.test.ts @@ -9,13 +9,13 @@ import {connect, disconnect, onPeerConnect, onPeerDisconnect} from "../../utils/ import {getNetworkForTest} from "../../utils/networkWithMockDb.js"; import {getValidPeerId} from "../../utils/peer.js"; -describe("network / main thread", function () { +describe("network / main thread", () => { vi.setConfig({testTimeout: 3000}); runTests({useWorker: false}); }); -describe("network / worker", function () { +describe("network / worker", () => { vi.setConfig({testTimeout: 10_000}); runTests({useWorker: true}); @@ -38,7 +38,6 @@ function runTests({useWorker}: {useWorker: boolean}): void { }); afterEach(() => controller.abort()); - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function createTestNode(nodeName: string) { const [network, closeAll] = await getNetworkForTest(nodeName, config, {opts: {useWorker}}); @@ -67,14 +66,14 @@ function runTests({useWorker}: {useWorker: boolean}): void { expect(networkIdentity.peerId).toBe(network.peerId.toString()); }); - it("should create a peer on connect", async function () { + it("should create a peer on connect", async () => { const [netA, netB] = await createTestNodesAB(); await Promise.all([onPeerConnect(netA), onPeerConnect(netB), connect(netA, netB)]); expect(netA.getConnectedPeerCount()).toBe(1); expect(netB.getConnectedPeerCount()).toBe(1); }); - it("should delete a peer on disconnect", async function () { + it("should delete a peer on disconnect", async () => { const [netA, netB] = await createTestNodesAB(); const connected = Promise.all([onPeerConnect(netA), onPeerConnect(netB)]); await connect(netA, netB); @@ -93,9 +92,9 @@ function runTests({useWorker}: {useWorker: boolean}): void { // Current implementation of discv5 consumer doesn't allow to deterministically force a peer to be found // a random find node lookup can yield no results if there are too few peers in the DHT - it.todo("should connect to new peer by subnet", async function () {}); + it.todo("should connect to new peer by subnet", async () => {}); - it("Should goodbye peers on stop", async function () { + it("Should goodbye peers on stop", async () => { const [netA, netB] = await createTestNodesAB(); const connected = Promise.all([onPeerConnect(netA), onPeerConnect(netB)]); 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 09c74f1f5dfe..12d9e393af09 100644 --- a/packages/beacon-node/test/e2e/network/onWorker/dataSerialization.test.ts +++ b/packages/beacon-node/test/e2e/network/onWorker/dataSerialization.test.ts @@ -29,8 +29,7 @@ import {EventDirection} from "../../../../src/util/workerEvents.js"; import {CommitteeSubscription} from "../../../../src/network/subnets/interface.js"; import {EchoWorker, getEchoWorker} from "./workerEchoHandler.js"; -// TODO: Need to find the way to load the echoWorker in the test environment -describe.skip("data serialization through worker boundary", function () { +describe("data serialization through worker boundary", () => { let echoWorker: EchoWorker; beforeAll(async () => { @@ -45,7 +44,7 @@ describe.skip("data serialization through worker boundary", function () { const peerId = validPeerIdStr; const peer = validPeerIdStr; const method = ReqRespMethod.BeaconBlocksByRange; - const bytes = ZERO_HASH; + const bytes = Uint8Array.from(ZERO_HASH); const statusZero = ssz.phase0.Status.defaultValue(); // Defining tests in this notation ensures that any event data is tested and probably safe to send @@ -90,7 +89,7 @@ describe.skip("data serialization through worker boundary", function () { type: BlockInputType.preData, block: ssz.capella.SignedBeaconBlock.defaultValue(), source: BlockSource.gossip, - blockBytes: ZERO_HASH, + blockBytes: Uint8Array.from(ZERO_HASH), }, peer, }, @@ -252,21 +251,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: 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; + const cachedData = { + fork: ForkName.deneb, + blobsCache: new Map(), + // Actual promise raise this error when used in `worker.postMessage` + // DataCloneError: # could not be cloned. + availabilityPromise: null, + // Actual function raise this error when used in `worker.postMessage` + // DataCloneError: function () { [native code] } could not be cloned + resolveAvailability: null, + } as unknown as CachedData; return { type: BlockInputType.dataPromise, block: ssz.deneb.SignedBeaconBlock.defaultValue(), source: BlockSource.gossip, - blockBytes: ZERO_HASH, + blockBytes: Uint8Array.from(ZERO_HASH), cachedData, }; } diff --git a/packages/beacon-node/test/e2e/network/onWorker/workerEcho.ts b/packages/beacon-node/test/e2e/network/onWorker/workerEcho.js similarity index 100% rename from packages/beacon-node/test/e2e/network/onWorker/workerEcho.ts rename to packages/beacon-node/test/e2e/network/onWorker/workerEcho.js diff --git a/packages/beacon-node/test/e2e/network/peers/peerManager.test.ts b/packages/beacon-node/test/e2e/network/peers/peerManager.test.ts index 795a4bf67ecc..d0b94399c01d 100644 --- a/packages/beacon-node/test/e2e/network/peers/peerManager.test.ts +++ b/packages/beacon-node/test/e2e/network/peers/peerManager.test.ts @@ -22,7 +22,7 @@ import {LocalStatusCache} from "../../../../src/network/statusCache.js"; const logger = testLogger("peerManager"); -describe("network / peers / PeerManager", function () { +describe("network / peers / PeerManager", () => { const peerId1 = getValidPeerId(); const afterEachCallbacks: (() => Promise | void)[] = []; @@ -33,7 +33,6 @@ describe("network / peers / PeerManager", function () { } }); - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function mockModules() { // Setup fake chain const block = ssz.phase0.SignedBeaconBlock.defaultValue(); @@ -155,7 +154,7 @@ describe("network / peers / PeerManager", function () { remotePeer: peerId1, } as Connection; - it("Should emit peer connected event on relevant peer status", async function () { + it("Should emit peer connected event on relevant peer status", async () => { const {statusCache, libp2p, networkEventBus} = await mockModules(); // Simualate a peer connection, get() should return truthy @@ -174,7 +173,7 @@ describe("network / peers / PeerManager", function () { await peerConnectedPromise; }); - it("On peerConnect handshake flow", async function () { + it("On peerConnect handshake flow", async () => { const {statusCache, libp2p, reqResp, peerManager, networkEventBus} = await mockModules(); // Simualate a peer connection, get() should return truthy diff --git a/packages/beacon-node/test/e2e/network/reqresp.test.ts b/packages/beacon-node/test/e2e/network/reqresp.test.ts index a3c8b7b66ca0..b7ab190166e7 100644 --- a/packages/beacon-node/test/e2e/network/reqresp.test.ts +++ b/packages/beacon-node/test/e2e/network/reqresp.test.ts @@ -15,13 +15,13 @@ import {PeerIdStr} from "../../../src/util/peerId.js"; /* eslint-disable require-yield, @typescript-eslint/naming-convention */ -describe("network / reqresp / main thread", function () { +describe("network / reqresp / main thread", () => { vi.setConfig({testTimeout: 3000}); runTests({useWorker: false}); }); -describe("network / reqresp / worker", function () { +describe("network / reqresp / worker", () => { vi.setConfig({testTimeout: 30_000}); runTests({useWorker: true}); @@ -76,7 +76,7 @@ function runTests({useWorker}: {useWorker: boolean}): void { return [netA, netB, await getPeerIdOf(netA), await getPeerIdOf(netB)]; } - it("should send/receive signed blocks", async function () { + it("should send/receive signed blocks", async () => { const req: phase0.BeaconBlocksByRangeRequest = {startSlot: 0, step: 1, count: 2}; const blocks: phase0.SignedBeaconBlock[] = []; for (let slot = req.startSlot; slot < req.count; slot++) { @@ -106,7 +106,7 @@ function runTests({useWorker}: {useWorker: boolean}): void { } }); - it("should send/receive a light client bootstrap message", async function () { + it("should send/receive a light client bootstrap message", async () => { const root: Root = ssz.phase0.BeaconBlockHeader.defaultValue().bodyRoot; const expectedValue = ssz.altair.LightClientBootstrap.defaultValue(); @@ -128,7 +128,7 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("should send/receive a light client optimistic update message", async function () { + it("should send/receive a light client optimistic update message", async () => { const expectedValue = ssz.altair.LightClientOptimisticUpdate.defaultValue(); const [netA, _, _0, peerIdB] = await createAndConnectPeers( @@ -149,7 +149,7 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("should send/receive a light client finality update message", async function () { + it("should send/receive a light client finality update message", async () => { const expectedValue = ssz.altair.LightClientFinalityUpdate.defaultValue(); const [netA, _, _0, peerIdB] = await createAndConnectPeers( @@ -170,7 +170,7 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("should send/receive a light client update message", async function () { + it("should send/receive a light client update message", async () => { const req: altair.LightClientUpdatesByRange = {startPeriod: 0, count: 2}; const lightClientUpdates: ResponseOutgoing[] = []; for (let slot = req.startPeriod; slot < req.count; slot++) { @@ -201,10 +201,11 @@ function runTests({useWorker}: {useWorker: boolean}): void { } }); - it("should handle a server error", async function () { + it("should handle a server error", async () => { const testErrorMessage = "TEST_EXAMPLE_ERROR_1234"; const [netA, _, _0, peerIdB] = await createAndConnectPeers( (method) => + // biome-ignore lint/correctness/useYield: No need for yield in test context async function* onRequest() { if (method === ReqRespMethod.BeaconBlocksByRange) { throw Error(testErrorMessage); @@ -218,7 +219,7 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("should handle a server error after emitting two blocks", async function () { + it("should handle a server error after emitting two blocks", async () => { const testErrorMessage = "TEST_EXAMPLE_ERROR_1234"; const [netA, _, _0, peerIdB] = await createAndConnectPeers( @@ -241,7 +242,7 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("trigger a TTFB_TIMEOUT error", async function () { + it("trigger a TTFB_TIMEOUT error", async () => { const ttfbTimeoutMs = 250; const [netA, _, _0, peerIdB] = await createAndConnectPeers( @@ -262,7 +263,7 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("trigger a RESP_TIMEOUT error", async function () { + it("trigger a RESP_TIMEOUT error", async () => { const respTimeoutMs = 250; const [netA, _, _0, peerIdB] = await createAndConnectPeers( @@ -284,9 +285,10 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("Sleep infinite on first byte", async function () { + it("Sleep infinite on first byte", async () => { const [netA, _, _0, peerIdB] = await createAndConnectPeers( (method) => + // biome-ignore lint/correctness/useYield: No need for yield in test context async function* onRequest() { if (method === ReqRespMethod.BeaconBlocksByRange) { await sleep(100000000); @@ -301,7 +303,7 @@ function runTests({useWorker}: {useWorker: boolean}): void { ); }); - it("Sleep infinite on second response chunk", async function () { + it("Sleep infinite on second response chunk", async () => { const [netA, _, _0, peerIdB] = await createAndConnectPeers( (method) => async function* onRequest() { diff --git a/packages/beacon-node/test/e2e/network/reqrespEncode.test.ts b/packages/beacon-node/test/e2e/network/reqrespEncode.test.ts index d96f405eae7c..ae916bdd0ab7 100644 --- a/packages/beacon-node/test/e2e/network/reqrespEncode.test.ts +++ b/packages/beacon-node/test/e2e/network/reqrespEncode.test.ts @@ -35,7 +35,6 @@ describe("reqresp encoder", () => { } }); - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function getLibp2p() { const listen = `/ip4/127.0.0.1/tcp/${port++}`; const libp2p = await createLibp2p({ @@ -50,11 +49,11 @@ describe("reqresp encoder", () => { return {libp2p, multiaddr: multiaddr(`${listen}/p2p/${libp2p.peerId.toString()}`)}; } - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function getReqResp(getHandler?: GetReqRespHandlerFn) { const {libp2p, multiaddr} = await getLibp2p(); const getHandlerNoop: GetReqRespHandlerFn = () => + // biome-ignore lint/correctness/useYield: No need for yield in test context async function* (): AsyncIterable { throw Error("not implemented"); }; @@ -76,7 +75,6 @@ describe("reqresp encoder", () => { return {libp2p, multiaddr, reqresp: new ReqRespBeaconNode(modules)}; } - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function dialProtocol({ dialer, toMultiaddr, diff --git a/packages/beacon-node/test/e2e/sync/finalizedSync.test.ts b/packages/beacon-node/test/e2e/sync/finalizedSync.test.ts index 01293285d756..ba98f2a1facd 100644 --- a/packages/beacon-node/test/e2e/sync/finalizedSync.test.ts +++ b/packages/beacon-node/test/e2e/sync/finalizedSync.test.ts @@ -14,13 +14,12 @@ import {ChainEvent} from "../../../src/chain/index.js"; import {connect, onPeerConnect} from "../../utils/network.js"; import {testLogger, LogLevel, TestLoggerOpts} from "../../utils/logger.js"; -describe("sync / finalized sync", function () { +describe("sync / finalized sync", () => { // chain is finalized at slot 32, plus 4 slots for genesis delay => ~72s it should sync pretty fast vi.setConfig({testTimeout: 90_000}); const validatorCount = 8; const testParams: Pick = { - // eslint-disable-next-line @typescript-eslint/naming-convention SECONDS_PER_SLOT: 2, }; @@ -32,7 +31,7 @@ describe("sync / finalized sync", function () { } }); - it("should do a finalized sync from another BN", async function () { + it("should do a finalized sync from another BN", async () => { // single node at beginning, use main thread to verify bls const genesisSlotsDelay = 4; const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; @@ -114,7 +113,7 @@ describe("sync / finalized sync", function () { try { await waitForSynced; loggerNodeB.info("Node B synced to Node A, received head block", {slot: head.message.slot}); - } catch (e) { + } catch (_e) { assert.fail("Failed to sync to other node in time"); } }); diff --git a/packages/beacon-node/test/e2e/sync/unknownBlockSync.test.ts b/packages/beacon-node/test/e2e/sync/unknownBlockSync.test.ts index 31f640269db7..290213d2b1ee 100644 --- a/packages/beacon-node/test/e2e/sync/unknownBlockSync.test.ts +++ b/packages/beacon-node/test/e2e/sync/unknownBlockSync.test.ts @@ -17,12 +17,11 @@ import {testLogger, LogLevel, TestLoggerOpts} from "../../utils/logger.js"; import {BlockError, BlockErrorCode} from "../../../src/chain/errors/index.js"; import {BlockSource, getBlockInput} from "../../../src/chain/blocks/types.js"; -describe("sync / unknown block sync", function () { +describe("sync / unknown block sync", () => { vi.setConfig({testTimeout: 40_000}); const validatorCount = 8; const testParams: Pick = { - // eslint-disable-next-line @typescript-eslint/naming-convention SECONDS_PER_SLOT: 2, }; @@ -46,7 +45,7 @@ describe("sync / unknown block sync", function () { ]; for (const {id, event} of testCases) { - it(id, async function () { + it(id, async () => { // the node needs time to transpile/initialize bls worker threads const genesisSlotsDelay = 4; const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; diff --git a/packages/beacon-node/test/globalSetup.ts b/packages/beacon-node/test/globalSetup.ts index 8194c76662df..f41a7f249b9b 100644 --- a/packages/beacon-node/test/globalSetup.ts +++ b/packages/beacon-node/test/globalSetup.ts @@ -11,7 +11,6 @@ export async function setup(): Promise { // Override FIELD_ELEMENTS_PER_BLOB if its a dev run, mostly to distinguish from // spec runs if (process.env.LODESTAR_PRESET === "minimal" && process.env.DEV_RUN) { - // eslint-disable-next-line @typescript-eslint/naming-convention setActivePreset(PresetName.minimal, {FIELD_ELEMENTS_PER_BLOB: 4096}); } } diff --git a/packages/beacon-node/test/memory/bytesHex.ts b/packages/beacon-node/test/memory/bytesHex.ts index 44115d345b2c..c7429869dac7 100644 --- a/packages/beacon-node/test/memory/bytesHex.ts +++ b/packages/beacon-node/test/memory/bytesHex.ts @@ -45,7 +45,6 @@ function testRunnerMemoryBpi(testCases: {getInstance: (bytes: number) => unknown convergeFactor: 0.2 / 100, }); - // eslint-disable-next-line no-console console.log(`${id.padEnd(longestId)} - ${bpi.toFixed(1)} bytes / instance`); } } diff --git a/packages/beacon-node/test/memory/pubkeysToIndex.ts b/packages/beacon-node/test/memory/pubkeysToIndex.ts index b647d153dba2..32cdf6e82296 100644 --- a/packages/beacon-node/test/memory/pubkeysToIndex.ts +++ b/packages/beacon-node/test/memory/pubkeysToIndex.ts @@ -52,7 +52,6 @@ function testRunnerMemoryBpi(testCases: {getInstance: (bytes: number) => unknown sampleEvery: 5, }); - // eslint-disable-next-line no-console console.log(`${id.padEnd(longestId)} - ${bpi.toFixed(1)} bytes / instance`); } } diff --git a/packages/beacon-node/test/memory/seenAttestationData.ts b/packages/beacon-node/test/memory/seenAttestationData.ts index c6735bd861e9..44a82dcd5841 100644 --- a/packages/beacon-node/test/memory/seenAttestationData.ts +++ b/packages/beacon-node/test/memory/seenAttestationData.ts @@ -53,7 +53,6 @@ function testRunnerMemoryBpi(testCases: {getInstance: (bytes: number) => unknown sampleEvery: 5, }); - // eslint-disable-next-line no-console console.log(`${id.padEnd(longestId)} - ${bpi.toFixed(1)} bytes / instance`); } } diff --git a/packages/beacon-node/test/memory/testRunnerMemory.ts b/packages/beacon-node/test/memory/testRunnerMemory.ts index a5c4df9e5794..05e7c8afab01 100644 --- a/packages/beacon-node/test/memory/testRunnerMemory.ts +++ b/packages/beacon-node/test/memory/testRunnerMemory.ts @@ -57,7 +57,6 @@ export async function testRunnerMemoryGc(opts: TestRunnerMemoryOpts): Prom usedMemoryArr.push(totalUsedMemoryDiff); const usedMemoryReg = linearRegression(xs, usedMemoryArr); - // eslint-disable-next-line no-console console.log("totalUsedMemoryDiff", totalUsedMemoryDiff, usedMemoryReg); } } @@ -137,7 +136,6 @@ export function testRunnerMemory(opts: TestRunnerMemoryOpts): number { } if (logEachSample) { - // eslint-disable-next-line no-console console.log(i, memoryUsage.rss / maxRssBytes, {m}); } diff --git a/packages/beacon-node/test/memory/unfinalizedPubkey2Index.ts b/packages/beacon-node/test/memory/unfinalizedPubkey2Index.ts index b37967d16ca4..1a317a7fe9d3 100644 --- a/packages/beacon-node/test/memory/unfinalizedPubkey2Index.ts +++ b/packages/beacon-node/test/memory/unfinalizedPubkey2Index.ts @@ -1,4 +1,5 @@ import crypto from "node:crypto"; +// biome-ignore lint/suspicious/noShadowRestrictedNames: We explicitly want `Map` name to be imported import {Map} from "immutable"; import {ValidatorIndex} from "@lodestar/types"; import {toMemoryEfficientHexStr} from "@lodestar/state-transition/src/cache/pubkeyCache.js"; @@ -48,7 +49,6 @@ function testRunnerMemoryBpi(testCases: {getInstance: (bytes: number) => unknown sampleEvery: 5, }); - // eslint-disable-next-line no-console console.log(`${id.padEnd(longestId)} - ${bpi.toFixed(1)} bytes / instance`); } } diff --git a/packages/beacon-node/test/mocks/beaconSyncMock.ts b/packages/beacon-node/test/mocks/beaconSyncMock.ts index 00ef193fdf6e..4d9f11ba90fd 100644 --- a/packages/beacon-node/test/mocks/beaconSyncMock.ts +++ b/packages/beacon-node/test/mocks/beaconSyncMock.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {Mocked, vi} from "vitest"; import {BeaconSync} from "../../src/sync/index.js"; diff --git a/packages/beacon-node/test/mocks/mockedBeaconChain.ts b/packages/beacon-node/test/mocks/mockedBeaconChain.ts index addeacf26a89..a69efc55836c 100644 --- a/packages/beacon-node/test/mocks/mockedBeaconChain.ts +++ b/packages/beacon-node/test/mocks/mockedBeaconChain.ts @@ -1,5 +1,5 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {vi, Mocked, Mock} from "vitest"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {config as defaultConfig} from "@lodestar/config/default"; import {ChainForkConfig} from "@lodestar/config"; import {ForkChoice, ProtoBlock, EpochDifference} from "@lodestar/fork-choice"; @@ -120,15 +120,15 @@ vi.mock("../../src/chain/chain.js", async (importActual) => { getClientVersion: vi.fn(), }, executionBuilder: {}, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error eth1: new Eth1ForBlockProduction(), opPool: new OpPool(), aggregatedAttestationPool: new AggregatedAttestationPool(config), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error beaconProposerCache: new BeaconProposerCache(), shufflingCache: new ShufflingCache(), + pubkey2index: new PubkeyIndexMap(), + index2pubkey: [], produceCommonBlockBody: vi.fn(), getProposerHead: vi.fn(), produceBlock: vi.fn(), @@ -138,6 +138,7 @@ vi.mock("../../src/chain/chain.js", async (importActual) => { predictProposerHead: vi.fn(), getHeadStateAtCurrentEpoch: vi.fn(), getHeadState: vi.fn(), + getStateBySlot: vi.fn(), updateBuilderStatus: vi.fn(), processBlock: vi.fn(), regenStateForAttestationVerification: vi.fn(), @@ -169,7 +170,6 @@ export type MockedBeaconChainOptions = { export function getMockedBeaconChain(opts?: Partial): MockedBeaconChain { const {clock, genesisTime, config} = opts ?? {}; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error return new BeaconChain({ clock: clock ?? "fake", diff --git a/packages/beacon-node/test/mocks/mockedBeaconDb.ts b/packages/beacon-node/test/mocks/mockedBeaconDb.ts index 8b99c85d2345..eb209c9b44fb 100644 --- a/packages/beacon-node/test/mocks/mockedBeaconDb.ts +++ b/packages/beacon-node/test/mocks/mockedBeaconDb.ts @@ -62,7 +62,6 @@ vi.mock("../../src/db/index.js", async (importActual) => { return { ...mod, - // eslint-disable-next-line @typescript-eslint/naming-convention BeaconDb: mockedBeaconDb, }; }); diff --git a/packages/beacon-node/test/mocks/mockedNetwork.ts b/packages/beacon-node/test/mocks/mockedNetwork.ts index ddf70aa1b5aa..9258acc9bc1b 100644 --- a/packages/beacon-node/test/mocks/mockedNetwork.ts +++ b/packages/beacon-node/test/mocks/mockedNetwork.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {vi, Mocked} from "vitest"; import {Network, INetwork} from "../../src/network/index.js"; diff --git a/packages/beacon-node/test/perf/api/impl/validator/attester.test.ts b/packages/beacon-node/test/perf/api/impl/validator/attester.test.ts index 803d6c84c408..807e29c55b52 100644 --- a/packages/beacon-node/test/perf/api/impl/validator/attester.test.ts +++ b/packages/beacon-node/test/perf/api/impl/validator/attester.test.ts @@ -1,5 +1,4 @@ import {itBench} from "@dapplion/benchmark"; -// eslint-disable-next-line import/no-relative-packages import {generatePerfTestCachedStatePhase0, numValidators} from "../../../../../../state-transition/test/perf/util.js"; import {getPubkeysForIndices} from "../../../../../src/api/impl/validator/utils.js"; import {linspace} from "../../../../../src/util/numpy.js"; diff --git a/packages/beacon-node/test/perf/bls/bls.test.ts b/packages/beacon-node/test/perf/bls/bls.test.ts index 988052f4a95c..9f1f90a4d879 100644 --- a/packages/beacon-node/test/perf/bls/bls.test.ts +++ b/packages/beacon-node/test/perf/bls/bls.test.ts @@ -11,7 +11,7 @@ import { } from "@chainsafe/blst"; import {linspace} from "../../../src/util/numpy.js"; -describe("BLS ops", function () { +describe("BLS ops", () => { type Keypair = {publicKey: PublicKey; secretKey: SecretKey}; // signature needs to be in Uint8Array to match real situation type BlsSet = {publicKey: PublicKey; message: Uint8Array; signature: Uint8Array}; 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 63fc4ee2e12c..89bd76f34eb3 100644 --- a/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts +++ b/packages/beacon-node/test/perf/chain/opPools/aggregatedAttestationPool.test.ts @@ -2,6 +2,7 @@ import {itBench} from "@dapplion/benchmark"; import {BitArray, toHexString} from "@chainsafe/ssz"; import { CachedBeaconStateAltair, + computeAnchorCheckpoint, computeEpochAtSlot, computeStartSlotAtEpoch, getBlockRootAtSlot, @@ -12,10 +13,8 @@ import {ExecutionStatus, ForkChoice, IForkChoiceStore, ProtoArray, DataAvailabil import {ssz} from "@lodestar/types"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; -// eslint-disable-next-line import/no-relative-packages import {generatePerfTestCachedStateAltair} from "../../../../../state-transition/test/perf/util.js"; import {AggregatedAttestationPool} from "../../../../src/chain/opPools/aggregatedAttestationPool.js"; -import {computeAnchorCheckpoint} from "../../../../src/chain/initState.js"; const vc = 1_500_000; diff --git a/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts b/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts index 6e420f0e1011..9d518c03eec9 100644 --- a/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts +++ b/packages/beacon-node/test/perf/chain/opPools/opPool.test.ts @@ -7,7 +7,6 @@ import { } from "@lodestar/params"; import {CachedBeaconStateAltair} from "@lodestar/state-transition"; import {ssz} from "@lodestar/types"; -// eslint-disable-next-line import/no-relative-packages import {generatePerfTestCachedStateAltair} from "../../../../../state-transition/test/perf/util.js"; import {OpPool} from "../../../../src/chain/opPools/opPool.js"; import {generateBlsToExecutionChanges} from "../../../fixtures/capella.js"; 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 7bf8c2f7252f..c08cf64fc974 100644 --- a/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts +++ b/packages/beacon-node/test/perf/chain/produceBlock/produceBlockBody.test.ts @@ -5,13 +5,12 @@ import {LevelDbController} from "@lodestar/db"; import {SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY} from "@lodestar/params"; import {defaultOptions as defaultValidatorOptions} from "@lodestar/validator"; import {CachedBeaconStateAltair} from "@lodestar/state-transition"; -// eslint-disable-next-line import/no-relative-packages import {generatePerfTestCachedStateAltair} from "../../../../../state-transition/test/perf/util.js"; import {BeaconChain} from "../../../../src/chain/index.js"; import {BlockType, produceBlockBody} from "../../../../src/chain/produceBlock/produceBlockBody.js"; import {Eth1ForBlockProductionDisabled} from "../../../../src/eth1/index.js"; import {ExecutionEngineDisabled} from "../../../../src/execution/engine/index.js"; -import {BeaconDb} from "../../../../src/index.js"; +import {StateArchiveMode, BeaconDb} from "../../../../src/index.js"; import {testLogger} from "../../../utils/logger.js"; const logger = testLogger(); @@ -37,6 +36,7 @@ describe("produceBlockBody", () => { skipCreateStateCacheIfAvailable: true, archiveStateEpochFrequency: 1024, minSameMessageSignatureSetsToBatch: 32, + stateArchiveMode: StateArchiveMode.Frequency, }, { config: state.config, diff --git a/packages/beacon-node/test/perf/chain/seenCache/seenAggregateAndProof.test.ts b/packages/beacon-node/test/perf/chain/seenCache/seenAggregateAndProof.test.ts index 8ba77ca8692f..49c3123c14dd 100644 --- a/packages/beacon-node/test/perf/chain/seenCache/seenAggregateAndProof.test.ts +++ b/packages/beacon-node/test/perf/chain/seenCache/seenAggregateAndProof.test.ts @@ -3,7 +3,7 @@ import {BitArray} from "@chainsafe/ssz"; import {TARGET_AGGREGATORS_PER_COMMITTEE} from "@lodestar/params"; import {SeenAggregatedAttestations} from "../../../../src/chain/seenCache/seenAggregateAndProof.js"; -describe("SeenAggregatedAttestations perf test", function () { +describe("SeenAggregatedAttestations perf test", () => { const targetEpoch = 2022; const attDataRoot = "0x55e1a1cce2aeb66f85b2285b8cb7aa55dfb67148b5e0067f0692b61ddbd2824b"; const fullByte = 0b11111111; diff --git a/packages/beacon-node/test/perf/chain/stateCache/inMemoryCheckpointsCache.test.ts b/packages/beacon-node/test/perf/chain/stateCache/inMemoryCheckpointsCache.test.ts index 3a4774655e7f..17a46b09af8d 100644 --- a/packages/beacon-node/test/perf/chain/stateCache/inMemoryCheckpointsCache.test.ts +++ b/packages/beacon-node/test/perf/chain/stateCache/inMemoryCheckpointsCache.test.ts @@ -4,7 +4,7 @@ import {ssz, phase0} from "@lodestar/types"; import {generateCachedState} from "../../../utils/state.js"; import {InMemoryCheckpointStateCache, toCheckpointHex} from "../../../../src/chain/stateCache/index.js"; -describe("InMemoryCheckpointStateCache perf tests", function () { +describe("InMemoryCheckpointStateCache perf tests", () => { setBenchOpts({noThreshold: true}); let state: CachedBeaconStateAllForks; diff --git a/packages/beacon-node/test/perf/chain/stateCache/updateUnfinalizedPubkeys.test.ts b/packages/beacon-node/test/perf/chain/stateCache/updateUnfinalizedPubkeys.test.ts index 46659ab3d287..a4bdbe9710cb 100644 --- a/packages/beacon-node/test/perf/chain/stateCache/updateUnfinalizedPubkeys.test.ts +++ b/packages/beacon-node/test/perf/chain/stateCache/updateUnfinalizedPubkeys.test.ts @@ -1,10 +1,11 @@ import {itBench, setBenchOpts} from "@dapplion/benchmark"; -import {Map} from "immutable"; +import {Map as ImmutableMap} from "immutable"; import {toBufferBE} from "bigint-buffer"; import {digest} from "@chainsafe/as-sha256"; import {SecretKey} from "@chainsafe/blst"; -import {ssz} from "@lodestar/types"; -import {type CachedBeaconStateAllForks, PubkeyIndexMap} from "@lodestar/state-transition"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; +import {ValidatorIndex, ssz} from "@lodestar/types"; +import {type CachedBeaconStateAllForks, toMemoryEfficientHexStr} from "@lodestar/state-transition"; import {bytesToBigInt, intToBytes} from "@lodestar/utils"; import {InMemoryCheckpointStateCache, BlockStateCacheImpl} from "../../../../src/chain/stateCache/index.js"; import {BlockStateCache} from "../../../../src/chain/stateCache/types.js"; @@ -14,7 +15,7 @@ import {generateCachedElectraState} from "../../../utils/state.js"; // ✔ updateUnfinalizedPubkeys - updating 10 pubkeys 1444.173 ops/s 692.4380 us/op - 1057 runs 6.03 s // ✔ updateUnfinalizedPubkeys - updating 100 pubkeys 189.5965 ops/s 5.274358 ms/op - 57 runs 1.15 s // ✔ updateUnfinalizedPubkeys - updating 1000 pubkeys 12.90495 ops/s 77.48967 ms/op - 13 runs 1.62 s -describe("updateUnfinalizedPubkeys perf tests", function () { +describe("updateUnfinalizedPubkeys perf tests", () => { setBenchOpts({noThreshold: true}); const numPubkeysToBeFinalizedCases = [10, 100, 1000]; @@ -31,7 +32,7 @@ describe("updateUnfinalizedPubkeys perf tests", function () { itBench({ id: `updateUnfinalizedPubkeys - updating ${numPubkeysToBeFinalized} pubkeys`, beforeEach: async () => { - baseState.epochCtx.unfinalizedPubkey2index = Map(unfinalizedPubkey2Index.map); + baseState.epochCtx.unfinalizedPubkey2index = ImmutableMap(unfinalizedPubkey2Index); baseState.epochCtx.pubkey2index = new PubkeyIndexMap(); baseState.epochCtx.index2pubkey = []; @@ -80,12 +81,14 @@ describe("updateUnfinalizedPubkeys perf tests", function () { }); } - function generatePubkey2Index(startIndex: number, endIndex: number): PubkeyIndexMap { - const pubkey2Index = new PubkeyIndexMap(); + type PubkeyHex = string; + + function generatePubkey2Index(startIndex: number, endIndex: number): Map { + const pubkey2Index = new Map(); const pubkeys = generatePubkeys(endIndex - startIndex); for (let i = startIndex; i < endIndex; i++) { - pubkey2Index.set(pubkeys[i], i); + pubkey2Index.set(toMemoryEfficientHexStr(pubkeys[i]), i); } return pubkey2Index; diff --git a/packages/beacon-node/test/perf/chain/validation/aggregateAndProof.test.ts b/packages/beacon-node/test/perf/chain/validation/aggregateAndProof.test.ts index 3fc7efe9d675..5e28a7c3b898 100644 --- a/packages/beacon-node/test/perf/chain/validation/aggregateAndProof.test.ts +++ b/packages/beacon-node/test/perf/chain/validation/aggregateAndProof.test.ts @@ -1,6 +1,5 @@ import {itBench} from "@dapplion/benchmark"; import {ssz} from "@lodestar/types"; -// eslint-disable-next-line import/no-relative-packages import {generateTestCachedBeaconStateOnlyValidators} from "../../../../../state-transition/test/perf/util.js"; import {validateApiAggregateAndProof, validateGossipAggregateAndProof} from "../../../../src/chain/validation/index.js"; import {getAggregateAndProofValidData} from "../../../utils/validationData/aggregateAndProof.js"; diff --git a/packages/beacon-node/test/perf/chain/validation/attestation.test.ts b/packages/beacon-node/test/perf/chain/validation/attestation.test.ts index f285317474d6..696b6bdb1871 100644 --- a/packages/beacon-node/test/perf/chain/validation/attestation.test.ts +++ b/packages/beacon-node/test/perf/chain/validation/attestation.test.ts @@ -1,9 +1,8 @@ import {itBench, setBenchOpts} from "@dapplion/benchmark"; import {expect} from "chai"; import {ssz} from "@lodestar/types"; -// eslint-disable-next-line import/no-relative-packages import {generateTestCachedBeaconStateOnlyValidators} from "../../../../../state-transition/test/perf/util.js"; -import {validateAttestation, validateGossipAttestationsSameAttData} from "../../../../src/chain/validation/index.js"; +import {validateGossipAttestationsSameAttData} from "../../../../src/chain/validation/index.js"; import {getAttestationValidData} from "../../../utils/validationData/attestation.js"; import {getAttDataFromAttestationSerialized} from "../../../../src/util/sszBytes.js"; @@ -29,25 +28,7 @@ describe("validate gossip attestation", () => { }); const attSlot = attestation0.data.slot; - const serializedData = ssz.phase0.Attestation.serialize(attestation0); const fork = chain.config.getForkName(stateSlot); - itBench({ - id: `validate gossip attestation - vc ${vc}`, - beforeEach: () => chain.seenAttesters["validatorIndexesByEpoch"].clear(), - fn: async () => { - await validateAttestation( - fork, - chain, - { - attestation: null, - serializedData, - attSlot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), - }, - subnet0 - ); - }, - }); for (const chunkSize of [32, 64, 128, 256]) { const attestations = [attestation0]; @@ -67,7 +48,7 @@ describe("validate gossip attestation", () => { attestation: null, serializedData, attSlot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }; }); diff --git a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts index a21c28adaec0..6f1cf2ef3da6 100644 --- a/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts +++ b/packages/beacon-node/test/perf/chain/verifyImportBlocks.test.ts @@ -4,23 +4,15 @@ import {SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY, SLOTS_PER_EPOCH} from "@lodestar/pa import {LevelDbController} from "@lodestar/db"; import {sleep} from "@lodestar/utils"; import {defaultOptions as defaultValidatorOptions} from "@lodestar/validator"; -// eslint-disable-next-line import/no-relative-packages import {rangeSyncTest} from "../../../../state-transition/test/perf/params.js"; -import { - getNetworkCachedState, - getNetworkCachedBlock, - // eslint-disable-next-line import/no-relative-packages -} from "../../../../state-transition/test/utils/testFileCache.js"; -import { - beforeValue, - // eslint-disable-next-line import/no-relative-packages -} from "../../../../state-transition/test/utils/beforeValueMocha.js"; +import {getNetworkCachedState, getNetworkCachedBlock} from "../../../../state-transition/test/utils/testFileCache.js"; +import {beforeValue} from "../../../../state-transition/test/utils/beforeValueMocha.js"; import {BeaconChain} from "../../../src/chain/index.js"; import {ExecutionEngineDisabled} from "../../../src/execution/engine/index.js"; import {Eth1ForBlockProductionDisabled} from "../../../src/eth1/index.js"; import {testLogger} from "../../utils/logger.js"; import {linspace} from "../../../src/util/numpy.js"; -import {BeaconDb} from "../../../src/index.js"; +import {StateArchiveMode, BeaconDb} from "../../../src/index.js"; import {getBlockInput, AttestationImportOpt, BlockSource} from "../../../src/chain/blocks/types.js"; // Define this params in `packages/state-transition/test/perf/params.ts` @@ -36,13 +28,22 @@ describe.skip("verify+import blocks - range sync perf test", () => { yieldEventLoopAfterEach: true, // So SubTree(s)'s WeakRef can be garbage collected https://github.com/nodejs/node/issues/39902 }); - before("Check correct params", () => { + let db: BeaconDb; + + before("Check correct params", async () => { // Must start at the first slot of the epoch to have a proper checkpoint state. // Using `computeStartSlotAtEpoch(...) - 1` will cause the chain to initialize with a state that's not the checkpoint // state, so processing the first block of the epoch will cause error `BLOCK_ERROR_WOULD_REVERT_FINALIZED_SLOT` if (rangeSyncTest.startSlot % SLOTS_PER_EPOCH !== 0) { throw Error("startSlot must be the first slot in the epoch"); } + + db = new BeaconDb(config, await LevelDbController.create({name: ".tmpdb"}, {logger})); + }); + + after(async () => { + // If before blocks fail, db won't be declared + if (db !== undefined) await db.close(); }); const blocks = beforeValue( @@ -65,15 +66,6 @@ describe.skip("verify+import blocks - range sync perf test", () => { return state; }, timeoutInfura); - let db: BeaconDb; - before(async () => { - db = new BeaconDb(config, await LevelDbController.create({name: ".tmpdb"}, {logger})); - }); - after(async () => { - // If before blocks fail, db won't be declared - if (db !== undefined) await db.close(); - }); - itBench({ id: `altair verifyImport ${network}_s${startSlot}:${slotCount}`, minRuns: 5, @@ -93,6 +85,7 @@ describe.skip("verify+import blocks - range sync perf test", () => { skipCreateStateCacheIfAvailable: true, archiveStateEpochFrequency: 1024, minSameMessageSignatureSetsToBatch: 32, + stateArchiveMode: StateArchiveMode.Frequency, }, { config: state.config, diff --git a/packages/beacon-node/test/perf/network/gossip/encoding.test.ts b/packages/beacon-node/test/perf/network/gossip/encoding.test.ts index 693c91f59249..e8e889de2194 100644 --- a/packages/beacon-node/test/perf/network/gossip/encoding.test.ts +++ b/packages/beacon-node/test/perf/network/gossip/encoding.test.ts @@ -8,7 +8,7 @@ import {toHex} from "@lodestar/utils"; ✔ Buffer.from 6696982 ops/s 149.3210 ns/op - 2023 runs 0.454 s ✔ shared Buffer 1.013911e+7 ops/s 98.62800 ns/op - 3083 runs 0.404 s */ -describe("encoding", function () { +describe("encoding", () => { const msgId = Uint8Array.from(Array.from({length: 20}, (_, i) => i)); const runsFactor = 1000; diff --git a/packages/beacon-node/test/perf/network/peers/util/prioritizePeers.test.ts b/packages/beacon-node/test/perf/network/peers/util/prioritizePeers.test.ts index a0d648a161ea..d5a6b4e9cb25 100644 --- a/packages/beacon-node/test/perf/network/peers/util/prioritizePeers.test.ts +++ b/packages/beacon-node/test/perf/network/peers/util/prioritizePeers.test.ts @@ -10,7 +10,7 @@ import {getAttnets, getSyncnets} from "../../../../utils/network.js"; describe("prioritizePeers", () => { const seedPeers: {id: PeerId; attnets: phase0.AttestationSubnets; syncnets: altair.SyncSubnets; score: number}[] = []; - before(async function () { + before(async () => { for (let i = 0; i < defaultNetworkOptions.maxPeers; i++) { const peer = await createSecp256k1PeerId(); peer.toString = () => `peer-${i}`; diff --git a/packages/beacon-node/test/perf/util/bytes.test.ts b/packages/beacon-node/test/perf/util/bytes.test.ts index d1d2e968b181..8c692bd3f92d 100644 --- a/packages/beacon-node/test/perf/util/bytes.test.ts +++ b/packages/beacon-node/test/perf/util/bytes.test.ts @@ -1,6 +1,6 @@ import {itBench} from "@dapplion/benchmark"; -describe("bytes utils", function () { +describe("bytes utils", () => { const roots: Uint8Array[] = []; let buffers: Buffer[] = []; const count = 32; diff --git a/packages/beacon-node/test/perf/util/dataview.test.ts b/packages/beacon-node/test/perf/util/dataview.test.ts index e0f28a5079e3..b0e394a0e1c8 100644 --- a/packages/beacon-node/test/perf/util/dataview.test.ts +++ b/packages/beacon-node/test/perf/util/dataview.test.ts @@ -1,6 +1,6 @@ import {itBench} from "@dapplion/benchmark"; -describe("dataview", function () { +describe("dataview", () => { const data = Uint8Array.from([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); itBench({ diff --git a/packages/beacon-node/test/perf/util/transferBytes.test.ts b/packages/beacon-node/test/perf/util/transferBytes.test.ts index 1cda12f7acd4..25cecbf770bc 100644 --- a/packages/beacon-node/test/perf/util/transferBytes.test.ts +++ b/packages/beacon-node/test/perf/util/transferBytes.test.ts @@ -1,7 +1,7 @@ import {itBench, setBenchOpts} from "@dapplion/benchmark"; import {expect} from "chai"; -describe("transfer bytes", function () { +describe("transfer bytes", () => { const sizes = [ {size: 84, name: "Status"}, {size: 112, name: "SignedVoluntaryExit"}, diff --git a/packages/beacon-node/test/scripts/blsPubkeyBytesFrequency.ts b/packages/beacon-node/test/scripts/blsPubkeyBytesFrequency.ts index 1889cacf1496..aaf882a86bff 100644 --- a/packages/beacon-node/test/scripts/blsPubkeyBytesFrequency.ts +++ b/packages/beacon-node/test/scripts/blsPubkeyBytesFrequency.ts @@ -58,7 +58,6 @@ function analyzeBytesFrequencies(pubkeys: string[]): void { byte0Freq[byte0] = 1 + (byte0Freq[byte0] ?? 0); } - // eslint-disable-next-line no-console console.log( `Byte[${i}] frequency distribution`, JSON.stringify( @@ -95,7 +94,6 @@ function analyzeBytesCollisions(pubkeys: string[]): void { } } - // eslint-disable-next-line no-console console.log(`bytes ${i}, collision rate ${collisions.size / 256 ** i}`); } } @@ -120,7 +118,6 @@ async function writePubkeys(): Promise { } run().catch((e) => { - // eslint-disable-next-line no-console console.error(e); process.exit(1); }); diff --git a/packages/beacon-node/test/setupPreset.ts b/packages/beacon-node/test/setupPreset.ts index b25c1d87835e..968c6e12c668 100644 --- a/packages/beacon-node/test/setupPreset.ts +++ b/packages/beacon-node/test/setupPreset.ts @@ -7,6 +7,5 @@ if (process.env.LODESTAR_PRESET === undefined) { // Override FIELD_ELEMENTS_PER_BLOB if its a dev run, mostly to distinguish from // spec runs if (process.env.LODESTAR_PRESET === "minimal" && process.env.DEV_RUN) { - // eslint-disable-next-line @typescript-eslint/naming-convention setActivePreset(PresetName.minimal, {FIELD_ELEMENTS_PER_BLOB: 4096}); } diff --git a/packages/beacon-node/test/sim/electra-interop.test.ts b/packages/beacon-node/test/sim/electra-interop.test.ts index d0c00e75fd59..20fedd3d4b0f 100644 --- a/packages/beacon-node/test/sim/electra-interop.test.ts +++ b/packages/beacon-node/test/sim/electra-interop.test.ts @@ -36,7 +36,7 @@ import {shell} from "./shell.js"; const jwtSecretHex = "0xdc6457099f127cf0bac78de8b297df04951281909db4f58b43def7c7151e765d"; const retries = defaultExecutionEngineHttpOpts.retries; const retryDelay = defaultExecutionEngineHttpOpts.retryDelay; -describe("executionEngine / ExecutionEngineHttp", function () { +describe("executionEngine / ExecutionEngineHttp", () => { if (!process.env.EL_BINARY_DIR || !process.env.EL_SCRIPT_DIR) { throw Error( `EL ENV must be provided, EL_BINARY_DIR: ${process.env.EL_BINARY_DIR}, EL_SCRIPT_DIR: ${process.env.EL_SCRIPT_DIR}` @@ -171,7 +171,6 @@ describe("executionEngine / ExecutionEngineHttp", function () { blockHash: dataToBytes(newPayloadBlockHash, 32), receiptsRoot: dataToBytes("0x0b67bea29f17eeb290685e01e9a2e4cd77a83471d9985a8ce27997a7ed3ee3f8", 32), blobGasUsed: 0n, - withdrawalRequests: [], }; const parentBeaconBlockRoot = dataToBytes("0x0000000000000000000000000000000000000000000000000000000000000000", 32); const payloadResult = await executionEngine.notifyNewPayload( @@ -208,22 +207,23 @@ describe("executionEngine / ExecutionEngineHttp", function () { await sleep(1000); const payloadAndBlockValue = await executionEngine.getPayload(ForkName.electra, payloadId2); const payload = payloadAndBlockValue.executionPayload as electra.ExecutionPayload; + const depositRequests = payloadAndBlockValue.executionRequests?.deposits; if (payload.transactions.length !== 1) { throw Error(`Number of transactions mismatched. Expected: 1, actual: ${payload.transactions.length}`); - } else { - const actualTransaction = bytesToData(payload.transactions[0]); + } - if (actualTransaction !== depositTransactionB) { - throw Error(`Transaction mismatched. Expected: ${depositTransactionB}, actual: ${actualTransaction}`); - } + const actualTransaction = bytesToData(payload.transactions[0]); + + if (actualTransaction !== depositTransactionB) { + throw Error(`Transaction mismatched. Expected: ${depositTransactionB}, actual: ${actualTransaction}`); } - if (payload.depositRequests.length !== 1) { - throw Error(`Number of depositRequests mismatched. Expected: 1, actual: ${payload.depositRequests.length}`); + if (depositRequests === undefined || depositRequests.length !== 1) { + throw Error(`Number of depositRequests mismatched. Expected: 1, actual: ${depositRequests?.length}`); } - const actualDepositRequest = payload.depositRequests[0]; + const actualDepositRequest = depositRequests[0]; assert.deepStrictEqual( actualDepositRequest, depositRequestB, @@ -234,7 +234,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { }); // TODO: get this post merge run working - it.skip("Post-merge, run for a few blocks", async function () { + it.skip("Post-merge, run for a few blocks", async () => { console.log("\n\nPost-merge, run for a few blocks\n\n"); const {elClient, tearDownCallBack} = await runEL( {...elSetupConfig, mode: ELStartMode.PostMerge, genesisTemplate: "electra.tmpl"}, @@ -331,7 +331,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { withEth1Credentials: true, }); - afterEachCallbacks.push(async function () { + afterEachCallbacks.push(async () => { await bn.close(); await sleep(1000); }); @@ -355,7 +355,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { valProposerConfig, }); - afterEachCallbacks.push(async function () { + afterEachCallbacks.push(async () => { await Promise.all(validators.map((v) => v.close())); }); diff --git a/packages/beacon-node/test/sim/mergemock.test.ts b/packages/beacon-node/test/sim/mergemock.test.ts index ee9839d58822..64020b070e11 100644 --- a/packages/beacon-node/test/sim/mergemock.test.ts +++ b/packages/beacon-node/test/sim/mergemock.test.ts @@ -29,7 +29,7 @@ import {shell} from "./shell.js"; const jwtSecretHex = "0xdc6457099f127cf0bac78de8b297df04951281909db4f58b43def7c7151e765d"; -describe("executionEngine / ExecutionEngineHttp", function () { +describe("executionEngine / ExecutionEngineHttp", () => { if (!process.env.EL_BINARY_DIR || !process.env.EL_SCRIPT_DIR) { throw Error( `EL ENV must be provided, EL_BINARY_DIR: ${process.env.EL_BINARY_DIR}, EL_SCRIPT_DIR: ${process.env.EL_SCRIPT_DIR}` @@ -64,7 +64,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { }); for (const useProduceBlockV3 of [false, true]) { - it(`Test builder with useProduceBlockV3=${useProduceBlockV3}`, async function () { + it(`Test builder with useProduceBlockV3=${useProduceBlockV3}`, async () => { console.log("\n\nPost-merge, run for a few blocks\n\n"); const {elClient, tearDownCallBack} = await runEL( {...elSetupConfig, mode: ELStartMode.PostMerge}, @@ -173,7 +173,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { // Enable builder by default, else because of circuit breaker we always start it with disabled bn.chain.executionBuilder.updateStatus(true); - afterEachCallbacks.push(async function () { + afterEachCallbacks.push(async () => { await bn.close(); await sleep(1000); }); @@ -204,7 +204,7 @@ describe("executionEngine / ExecutionEngineHttp", function () { useProduceBlockV3, }); - afterEachCallbacks.push(async function () { + afterEachCallbacks.push(async () => { await Promise.all(validators.map((v) => v.close())); }); diff --git a/packages/beacon-node/test/spec/bls/bls.ts b/packages/beacon-node/test/spec/bls/bls.ts index 5af15bcb8eb2..76c511215b61 100644 --- a/packages/beacon-node/test/spec/bls/bls.ts +++ b/packages/beacon-node/test/spec/bls/bls.ts @@ -10,8 +10,6 @@ import { } from "@chainsafe/blst"; import {fromHexString} from "@chainsafe/ssz"; -/* eslint-disable @typescript-eslint/naming-convention */ - export const testFnByType: Record any)> = { aggregate_verify, aggregate, @@ -44,7 +42,7 @@ function aggregate_verify(input: {pubkeys: string[]; messages: string[]; signatu pubkeys.map((pk) => PublicKey.fromHex(pk)), Signature.fromHex(signature) ); - } catch (e) { + } catch (_e) { return false; } } @@ -78,7 +76,7 @@ function fast_aggregate_verify(input: {pubkeys: string[]; message: string; signa pubkeys.map((hex) => PublicKey.fromHex(hex, true)), Signature.fromHex(signature, true) ); - } catch (e) { + } catch (_e) { return false; } } @@ -103,7 +101,7 @@ function batch_verify(input: {pubkeys: string[]; messages: string[]; signatures: sig: Signature.fromHex(signatures[i], true), })) ); - } catch (e) { + } catch (_e) { return false; } } @@ -137,7 +135,7 @@ function verify(input: {pubkey: string; message: string; signature: string}): bo const {pubkey, message, signature} = input; try { return _verify(fromHexString(message), PublicKey.fromHex(pubkey), Signature.fromHex(signature)); - } catch (e) { + } catch (_e) { return false; } } @@ -153,7 +151,7 @@ function deserialization_G1(input: {pubkey: string}): boolean { try { PublicKey.fromHex(input.pubkey, true); return true; - } catch (e) { + } catch (_e) { return false; } } @@ -169,7 +167,7 @@ function deserialization_G2(input: {signature: string}): boolean { try { Signature.fromHex(input.signature, true); return true; - } catch (e) { + } catch (_e) { return false; } } diff --git a/packages/beacon-node/test/spec/bls/index.test.ts b/packages/beacon-node/test/spec/bls/index.test.ts index b781685432e6..32baa00d9fbc 100644 --- a/packages/beacon-node/test/spec/bls/index.test.ts +++ b/packages/beacon-node/test/spec/bls/index.test.ts @@ -35,7 +35,7 @@ for (const fnName of readdirSyncSpec(blsSpecTests.outputDir)) { const fnTestDirpath = path.join(blsSpecTests.outputDir, fnName); for (const testName of readdirSyncSpec(fnTestDirpath)) { - it(`${fnName}/${testName}`, function (context) { + it(`${fnName}/${testName}`, (context) => { if (fn === "skip") { context.skip(); return; diff --git a/packages/beacon-node/test/spec/general/bls.ts b/packages/beacon-node/test/spec/general/bls.ts index 010a5b9be53d..128b5b4f5613 100644 --- a/packages/beacon-node/test/spec/general/bls.ts +++ b/packages/beacon-node/test/spec/general/bls.ts @@ -5,21 +5,19 @@ import { Signature, aggregateSerializedPublicKeys, aggregateSignatures, - aggregateVerify, - fastAggregateVerify, + aggregateVerify as BLSAggregateVerify, + fastAggregateVerify as BLSFastAggregateVerify, verify as _verify, } from "@chainsafe/blst"; import {InputType} from "@lodestar/spec-test-util"; import {TestRunnerFn} from "../utils/types.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - const testFnByType: Record any> = { aggregate, - aggregate_verify, - eth_aggregate_pubkeys, - eth_fast_aggregate_verify, - fast_aggregate_verify, + aggregate_verify: aggregateVerify, + eth_aggregate_pubkeys: ethAggregatePubkeys, + eth_fast_aggregate_verify: ethFastAggregateVerify, + fast_aggregate_verify: fastAggregateVerify, sign, verify, }; @@ -29,7 +27,7 @@ const G2_POINT_AT_INFINITY = const G1_POINT_AT_INFINITY = "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; -export const blsTestRunner: TestRunnerFn = (fork, testName) => { +export const blsTestRunner: TestRunnerFn = (_fork, testName) => { return { testFunction: ({data}) => { const testFn = testFnByType[testName]; @@ -43,9 +41,8 @@ export const blsTestRunner: TestRunnerFn = (fork, testName const {message} = e as Error; if (message.includes("BLST_ERROR") || message === "EMPTY_AGGREGATE_ARRAY" || message === "ZERO_SECRET_KEY") { return null; - } else { - throw e; } + throw e; } }, options: { @@ -75,7 +72,7 @@ function aggregate(input: string[]): string | null { const pks = input.map((pkHex) => Signature.fromHex(pkHex)); const agg = aggregateSignatures(pks); return agg.toHex(); - } catch (e) { + } catch (_e) { return null; } } @@ -89,15 +86,15 @@ function aggregate(input: string[]): string | null { * output: bool -- true (VALID) or false (INVALID) * ``` */ -function aggregate_verify(input: {pubkeys: string[]; messages: string[]; signature: string}): boolean { +function aggregateVerify(input: {pubkeys: string[]; messages: string[]; signature: string}): boolean { const {pubkeys, messages, signature} = input; try { - return aggregateVerify( + return BLSAggregateVerify( messages.map(fromHexString), pubkeys.map((pk) => PublicKey.fromHex(pk)), Signature.fromHex(signature) ); - } catch (e) { + } catch (_e) { return false; } } @@ -108,7 +105,7 @@ function aggregate_verify(input: {pubkeys: string[]; messages: string[]; signatu * output: BLS Signature -- expected output, single BLS signature or empty. * ``` */ -function eth_aggregate_pubkeys(input: string[]): string | null { +function ethAggregatePubkeys(input: string[]): string | null { // Don't add this checks in the source as beacon nodes check the pubkeys for inf when onboarding for (const pk of input) { if (pk === G1_POINT_AT_INFINITY) return null; @@ -116,7 +113,7 @@ function eth_aggregate_pubkeys(input: string[]): string | null { try { return aggregateSerializedPublicKeys(input.map((hex) => fromHexString(hex))).toHex(); - } catch (e) { + } catch (_e) { return null; } } @@ -130,7 +127,7 @@ function eth_aggregate_pubkeys(input: string[]): string | null { * output: bool -- true (VALID) or false (INVALID) * ``` */ -function eth_fast_aggregate_verify(input: {pubkeys: string[]; message: string; signature: string}): boolean { +function ethFastAggregateVerify(input: {pubkeys: string[]; message: string; signature: string}): boolean { const {pubkeys, message, signature} = input; if (pubkeys.length === 0 && signature === G2_POINT_AT_INFINITY) { @@ -143,12 +140,12 @@ function eth_fast_aggregate_verify(input: {pubkeys: string[]; message: string; s } try { - return fastAggregateVerify( + return BLSFastAggregateVerify( fromHexString(message), pubkeys.map((hex) => PublicKey.fromHex(hex)), Signature.fromHex(signature) ); - } catch (e) { + } catch (_e) { return false; } } @@ -162,15 +159,15 @@ function eth_fast_aggregate_verify(input: {pubkeys: string[]; message: string; s * output: bool -- true (VALID) or false (INVALID) * ``` */ -function fast_aggregate_verify(input: {pubkeys: string[]; message: string; signature: string}): boolean | null { +function fastAggregateVerify(input: {pubkeys: string[]; message: string; signature: string}): boolean | null { const {pubkeys, message, signature} = input; try { - return fastAggregateVerify( + return BLSFastAggregateVerify( fromHexString(message), pubkeys.map((hex) => PublicKey.fromHex(hex, true)), Signature.fromHex(signature, true) ); - } catch (e) { + } catch (_e) { return false; } } @@ -185,7 +182,7 @@ function sign(input: {privkey: string; message: string}): string | null { const {privkey, message} = input; try { return SecretKey.fromHex(privkey).sign(fromHexString(message)).toHex(); - } catch (e) { + } catch (_e) { return null; } } @@ -201,7 +198,7 @@ function verify(input: {pubkey: string; message: string; signature: string}): bo const {pubkey, message, signature} = input; try { return _verify(fromHexString(message), PublicKey.fromHex(pubkey), Signature.fromHex(signature)); - } catch (e) { + } catch (_e) { return false; } } diff --git a/packages/beacon-node/test/spec/general/index.test.ts b/packages/beacon-node/test/spec/general/index.test.ts index f6bfb0b7b2a1..063f128c3142 100644 --- a/packages/beacon-node/test/spec/general/index.test.ts +++ b/packages/beacon-node/test/spec/general/index.test.ts @@ -5,8 +5,6 @@ import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; import {blsTestRunner} from "./bls.js"; import {sszGeneric} from "./ssz_generic.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - // NOTE: You MUST always provide a detailed reason of why a spec test is skipped plus link // to an issue marking it as pending to re-enable and an aproximate timeline of when it will // be fixed. diff --git a/packages/beacon-node/test/spec/general/ssz_generic.ts b/packages/beacon-node/test/spec/general/ssz_generic.ts index e791ca62eefe..64e6b4455941 100644 --- a/packages/beacon-node/test/spec/general/ssz_generic.ts +++ b/packages/beacon-node/test/spec/general/ssz_generic.ts @@ -15,7 +15,7 @@ import {getTestType} from "./ssz_generic_types.js"; export const sszGeneric = (skippedTypes: string[]): TestRunnerCustom => - (fork, typeName, testSuite, testSuiteDirpath) => { + (_fork, typeName, testSuite, testSuiteDirpath) => { if (testSuite === "invalid") { for (const testCase of fs.readdirSync(testSuiteDirpath)) { it(testCase, () => { diff --git a/packages/beacon-node/test/spec/general/ssz_generic_types.ts b/packages/beacon-node/test/spec/general/ssz_generic_types.ts index cd95a5dec79e..fe19f08149b4 100644 --- a/packages/beacon-node/test/spec/general/ssz_generic_types.ts +++ b/packages/beacon-node/test/spec/general/ssz_generic_types.ts @@ -20,8 +20,6 @@ const uint64 = new UintBigintType(8); const uint128 = new UintBigintType(16); const uint256 = new UintBigintType(32); -/* eslint-disable @typescript-eslint/naming-convention */ - // class SingleFieldTestStruct(Container): // A: byte const SingleFieldTestStruct = new ContainerType({ @@ -122,7 +120,7 @@ export function getTestType(testType: string, testCase: string): Type { const elementType = vecElementTypes[elementTypeStr as keyof typeof vecElementTypes]; if (elementType === undefined) throw Error(`No vecElementType for ${elementTypeStr}: '${testCase}'`); const length = parseInt(lengthStr); - if (isNaN(length)) throw Error(`Bad length ${length}: '${testCase}'`); + if (Number.isNaN(length)) throw Error(`Bad length ${length}: '${testCase}'`); return new VectorBasicType(elementType, length); } @@ -175,6 +173,6 @@ export function getTestType(testType: string, testCase: string): Type { function parseSecondNum(str: string, id: string): number { const match = str.match(/[^\W_]+_([0-9]+)/); const num = parseInt((match || [])[1]); - if (isNaN(num)) throw Error(`Bad ${id} ${str}`); + if (Number.isNaN(num)) throw Error(`Bad ${id} ${str}`); return num; } diff --git a/packages/beacon-node/test/spec/presets/epoch_processing.test.ts b/packages/beacon-node/test/spec/presets/epoch_processing.test.ts index 604243400aa0..c61aec40fee3 100644 --- a/packages/beacon-node/test/spec/presets/epoch_processing.test.ts +++ b/packages/beacon-node/test/spec/presets/epoch_processing.test.ts @@ -20,8 +20,6 @@ import {specTestIterator} from "../utils/specTestIterator.js"; export type EpochTransitionFn = (state: CachedBeaconStateAllForks, epochTransitionCache: EpochTransitionCache) => void; -/* eslint-disable @typescript-eslint/naming-convention */ - const epochTransitionFns: Record = { effective_balance_updates: (state, epochTransitionCache) => { const fork = state.config.getForkSeq(state.slot); @@ -46,7 +44,7 @@ const epochTransitionFns: Record = { epochFns.processSyncCommitteeUpdates(fork, state as CachedBeaconStateAltair); }, historical_summaries_update: epochFns.processHistoricalSummariesUpdate as EpochTransitionFn, - pending_balance_deposits: epochFns.processPendingBalanceDeposits as EpochTransitionFn, + pending_deposits: epochFns.processPendingDeposits as EpochTransitionFn, pending_consolidations: epochFns.processPendingConsolidations as EpochTransitionFn, }; @@ -99,12 +97,12 @@ const epochProcessing = post: ssz[fork].BeaconState, }, getExpected: (testCase) => testCase.post, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(fork, expected, actual); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts shouldSkip: (_testcase, name, _index) => - skipTestNames !== undefined && skipTestNames.some((skipTestName) => name.includes(skipTestName)), + skipTestNames?.some((skipTestName) => name.includes(skipTestName)) ?? false, }, }; }; diff --git a/packages/beacon-node/test/spec/presets/finality.test.ts b/packages/beacon-node/test/spec/presets/finality.test.ts index 0ec4017064f4..1dce91e360e5 100644 --- a/packages/beacon-node/test/spec/presets/finality.test.ts +++ b/packages/beacon-node/test/spec/presets/finality.test.ts @@ -15,8 +15,6 @@ import {assertCorrectProgressiveBalances} from "../config.js"; import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; import {specTestIterator} from "../utils/specTestIterator.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - const finality: TestRunnerFn = (fork) => { return { testFunction: (testcase) => { @@ -49,7 +47,7 @@ const finality: TestRunnerFn = (fork) => shouldError: (testCase) => !testCase.post, timeout: 10000, getExpected: (testCase) => testCase.post, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(fork, expected, actual); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts diff --git a/packages/beacon-node/test/spec/presets/fork.test.ts b/packages/beacon-node/test/spec/presets/fork.test.ts index c121e651fcea..626d6477bcb1 100644 --- a/packages/beacon-node/test/spec/presets/fork.test.ts +++ b/packages/beacon-node/test/spec/presets/fork.test.ts @@ -50,7 +50,7 @@ const fork: TestRunnerFn = (forkNext) => { timeout: 10000, shouldError: (testCase) => testCase.post === undefined, getExpected: (testCase) => testCase.post, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(forkNext, expected, actual); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts 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 7cb6e3c3d692..72515e703582 100644 --- a/packages/beacon-node/test/spec/presets/fork_choice.test.ts +++ b/packages/beacon-node/test/spec/presets/fork_choice.test.ts @@ -46,8 +46,6 @@ import {initCKZG, loadEthereumTrustedSetup} from "../../../src/util/kzg.js"; import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; import {specTestIterator} from "../utils/specTestIterator.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - const ANCHOR_STATE_FILE_NAME = "anchor_state"; const ANCHOR_BLOCK_FILE_NAME = "anchor_block"; const BLOCK_FILE_NAME = "^(block)_([0-9a-zA-Z]+)$"; diff --git a/packages/beacon-node/test/spec/presets/genesis.test.ts b/packages/beacon-node/test/spec/presets/genesis.test.ts index 773debe3bb19..140fa3686e67 100644 --- a/packages/beacon-node/test/spec/presets/genesis.test.ts +++ b/packages/beacon-node/test/spec/presets/genesis.test.ts @@ -22,8 +22,6 @@ import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; // The aim of the genesis tests is to provide a baseline to test genesis-state initialization and test if the // proposed genesis-validity conditions are working. -/* eslint-disable @typescript-eslint/naming-convention */ - const genesis: TestRunnerFn = (fork, testName, testSuite) => { const testFn = genesisTestFns[testName]; if (testFn === undefined) { @@ -86,7 +84,7 @@ const genesisInitialization: TestRunnerFn testCase.state, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(fork, expected, actual); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts @@ -108,7 +106,7 @@ const genesisValidity: TestRunnerFn = (fork) = genesis: ssz[fork].BeaconState, }, getExpected: (testCase) => testCase.is_valid, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expect(actual).toEqualWithMessage(expected, "isValidGenesisState is not" + expected); }, }, diff --git a/packages/beacon-node/test/spec/presets/light_client/index.test.ts b/packages/beacon-node/test/spec/presets/light_client/index.test.ts index 0a44772cab4b..a8cc12238675 100644 --- a/packages/beacon-node/test/spec/presets/light_client/index.test.ts +++ b/packages/beacon-node/test/spec/presets/light_client/index.test.ts @@ -7,8 +7,6 @@ import {singleMerkleProof} from "./single_merkle_proof.js"; import {sync} from "./sync.js"; import {updateRanking} from "./update_ranking.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - const lightClient: TestRunnerFn = (fork, testName, testSuite) => { const testFn = lightclientTestFns[testName]; if (testFn === undefined) { diff --git a/packages/beacon-node/test/spec/presets/light_client/single_merkle_proof.ts b/packages/beacon-node/test/spec/presets/light_client/single_merkle_proof.ts index d230bc926b0d..08dcd0ab8acf 100644 --- a/packages/beacon-node/test/spec/presets/light_client/single_merkle_proof.ts +++ b/packages/beacon-node/test/spec/presets/light_client/single_merkle_proof.ts @@ -7,8 +7,6 @@ import {ForkName} from "@lodestar/params"; import {toHex} from "@lodestar/utils"; import {TestRunnerFn} from "../../utils/types.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - // https://github.com/ethereum/consensus-specs/blob/da3f5af919be4abb5a6db5a80b235deb8b4b5cba/tests/formats/light_client/single_merkle_proof.md type SingleMerkleProofTestCase = { meta?: any; @@ -23,7 +21,11 @@ type SingleMerkleProofTestCase = { }; }; -export const singleMerkleProof: TestRunnerFn = (fork, testHandler, testSuite) => { +export const singleMerkleProof: TestRunnerFn = ( + fork, + _testHandler, + testSuite +) => { return { testFunction: (testcase) => { // Assert correct proof generation @@ -39,7 +41,7 @@ export const singleMerkleProof: TestRunnerFn testCase.proof.branch, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expect(actual).deep.equals(expected); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts 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 bfd3d0d0bb3d..3e82256fab1d 100644 --- a/packages/beacon-node/test/spec/presets/light_client/sync.ts +++ b/packages/beacon-node/test/spec/presets/light_client/sync.ts @@ -9,8 +9,6 @@ import {computeSyncPeriodAtSlot} from "@lodestar/state-transition"; import {TestRunnerFn} from "../../utils/types.js"; import {testLogger} from "../../../utils/logger.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - // https://github.com/ethereum/consensus-specs/blob/da3f5af919be4abb5a6db5a80b235deb8b4b5cba/tests/formats/light_client/single_merkle_proof.md type SyncTestCase = { meta: { @@ -130,7 +128,7 @@ export const sync: TestRunnerFn = (fork) => { } const headerSlot = Number(step.process_update.checks.optimistic_header.slot); - const update = config.getLightClientForkTypes(headerSlot)["LightClientUpdate"].deserialize(updateBytes); + const update = config.getLightClientForkTypes(headerSlot).LightClientUpdate.deserialize(updateBytes); logger.debug(`LightclientUpdateSummary: ${JSON.stringify(toLightClientUpdateSummary(update))}`); 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 a2e60c1cc84a..b51219dd4e54 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 @@ -5,8 +5,6 @@ import {InputType} from "@lodestar/spec-test-util"; import {isBetterUpdate, LightClientUpdateSummary, toLightClientUpdateSummary} from "@lodestar/light-client/spec"; import {TestRunnerFn} from "../../utils/types.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - // https://github.com/ethereum/consensus-specs/blob/da3f5af919be4abb5a6db5a80b235deb8b4b5cba/tests/formats/light_client/update_ranking.md type UpdateRankingTestCase = { meta: { diff --git a/packages/beacon-node/test/spec/presets/merkle.test.ts b/packages/beacon-node/test/spec/presets/merkle.test.ts index d3f6890527e9..71cebdbd0b5b 100644 --- a/packages/beacon-node/test/spec/presets/merkle.test.ts +++ b/packages/beacon-node/test/spec/presets/merkle.test.ts @@ -11,8 +11,6 @@ import {RunnerType, TestRunnerFn} from "../utils/types.js"; import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; import {specTestIterator} from "../utils/specTestIterator.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - const merkle: TestRunnerFn = (fork) => { return { testFunction: (testcase) => { @@ -47,7 +45,7 @@ const merkle: TestRunnerFn = (fork) => { }), timeout: 10000, getExpected: (testCase) => testCase.proof, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expect(actual).toEqualWithMessage(expected, "incorrect proof"); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts diff --git a/packages/beacon-node/test/spec/presets/operations.test.ts b/packages/beacon-node/test/spec/presets/operations.test.ts index 7e2e9c1e9c5d..4c90831ef155 100644 --- a/packages/beacon-node/test/spec/presets/operations.test.ts +++ b/packages/beacon-node/test/spec/presets/operations.test.ts @@ -20,10 +20,8 @@ import {BaseSpecTest, RunnerType, shouldVerify, TestRunnerFn} from "../utils/typ import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; import {specTestIterator} from "../utils/specTestIterator.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - // Define above to re-use in sync_aggregate and sync_aggregate_random -const sync_aggregate: BlockProcessFn = ( +const syncAggregate: BlockProcessFn = ( state, testCase: {sync_aggregate: altair.SyncAggregate} ) => { @@ -62,8 +60,8 @@ const operationFns: Record> = blockFns.processProposerSlashing(fork, state, testCase.proposer_slashing); }, - sync_aggregate, - sync_aggregate_random: sync_aggregate, + sync_aggregate: syncAggregate, + sync_aggregate_random: syncAggregate, voluntary_exit: (state, testCase: {voluntary_exit: phase0.SignedVoluntaryExit}) => { const fork = state.config.getForkSeq(state.slot); @@ -94,8 +92,7 @@ const operationFns: Record> = }, deposit_request: (state, testCase: {deposit_request: electra.DepositRequest}) => { - const fork = state.config.getForkSeq(state.slot); - blockFns.processDepositRequest(fork, state as CachedBeaconStateElectra, testCase.deposit_request); + blockFns.processDepositRequest(state as CachedBeaconStateElectra, testCase.deposit_request); }, consolidation_request: (state, testCase: {consolidation_request: electra.ConsolidationRequest}) => { @@ -156,7 +153,7 @@ const operations: TestRunnerFn = (fork, }, shouldError: (testCase) => testCase.post === undefined, getExpected: (testCase) => testCase.post, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(fork, expected, actual); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts diff --git a/packages/beacon-node/test/spec/presets/rewards.test.ts b/packages/beacon-node/test/spec/presets/rewards.test.ts index 426245242d84..df43c8ca612e 100644 --- a/packages/beacon-node/test/spec/presets/rewards.test.ts +++ b/packages/beacon-node/test/spec/presets/rewards.test.ts @@ -13,8 +13,6 @@ import {assertCorrectProgressiveBalances} from "../config.js"; import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; import {specTestIterator} from "../utils/specTestIterator.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - const deltasType = new VectorCompositeType(ssz.phase0.Balances, 2); const rewards: TestRunnerFn = (fork) => { @@ -57,7 +55,7 @@ const rewards: TestRunnerFn = (fork) => { ...(testCase.inclusion_delay_deltas ? [testCase.inclusion_delay_deltas] : []), testCase.inactivity_penalty_deltas, ]), - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expect(actual).toEqual(expected); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts diff --git a/packages/beacon-node/test/spec/presets/sanity.test.ts b/packages/beacon-node/test/spec/presets/sanity.test.ts index 3ec1efb84fde..cd266483c7f8 100644 --- a/packages/beacon-node/test/spec/presets/sanity.test.ts +++ b/packages/beacon-node/test/spec/presets/sanity.test.ts @@ -18,8 +18,6 @@ import {assertCorrectProgressiveBalances} from "../config.js"; import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; import {specTestIterator} from "../utils/specTestIterator.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - const sanity: TestRunnerFn = (fork, testName, testSuite) => { switch (testName) { case "slots": @@ -50,7 +48,7 @@ const sanitySlots: TestRunnerFn = (for shouldError: (testCase) => !testCase.post, timeout: 10000, getExpected: (testCase) => testCase.post, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(fork, expected, actual); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts @@ -88,7 +86,7 @@ const sanityBlocks: TestRunnerFn = (f shouldError: (testCase) => testCase.post === undefined, timeout: 10000, getExpected: (testCase) => testCase.post, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(fork, expected, actual); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts diff --git a/packages/beacon-node/test/spec/presets/shuffling.test.ts b/packages/beacon-node/test/spec/presets/shuffling.test.ts index 06e7d4717d06..7b0f38ecf581 100644 --- a/packages/beacon-node/test/spec/presets/shuffling.test.ts +++ b/packages/beacon-node/test/spec/presets/shuffling.test.ts @@ -1,24 +1,27 @@ import path from "node:path"; -import {unshuffleList} from "@lodestar/state-transition"; +import {unshuffleList} from "@chainsafe/swap-or-not-shuffle"; import {InputType} from "@lodestar/spec-test-util"; import {bnToNum, fromHex} from "@lodestar/utils"; -import {ACTIVE_PRESET} from "@lodestar/params"; +import {ACTIVE_PRESET, SHUFFLE_ROUND_COUNT} from "@lodestar/params"; import {RunnerType, TestRunnerFn} from "../utils/types.js"; import {ethereumConsensusSpecsTests} from "../specTestVersioning.js"; import {specTestIterator} from "../utils/specTestIterator.js"; -const shuffling: TestRunnerFn = () => { +const shuffling: TestRunnerFn = () => { return { testFunction: (testcase) => { const seed = fromHex(testcase.mapping.seed); - const output = Array.from({length: bnToNum(testcase.mapping.count)}, (_, i) => i); - unshuffleList(output, seed); - return output; + const output = unshuffleList( + Uint32Array.from(Array.from({length: bnToNum(testcase.mapping.count)}, (_, i) => i)), + seed, + SHUFFLE_ROUND_COUNT + ); + return Buffer.from(output).toString("hex"); }, options: { inputTypes: {mapping: InputType.YAML}, timeout: 10000, - getExpected: (testCase) => testCase.mapping.mapping.map((value) => bnToNum(value)), + getExpected: (testCase) => Buffer.from(testCase.mapping.mapping.map((value) => bnToNum(value))).toString("hex"), // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts }, }; 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 6e43d851ef66..f5cdcc719b6a 100644 --- a/packages/beacon-node/test/spec/presets/ssz_static.test.ts +++ b/packages/beacon-node/test/spec/presets/ssz_static.test.ts @@ -32,7 +32,7 @@ type Types = Record>; const sszStatic = (skippedFork: string, skippedTypes?: string[]) => - (fork: ForkName, typeName: string, testSuite: string, testSuiteDirpath: string): void => { + (fork: ForkName, typeName: string, _testSuite: string, testSuiteDirpath: string): void => { if (fork === skippedFork) { return; } @@ -52,7 +52,7 @@ const sszStatic = (ssz.altair as Types)[typeName] || (ssz.phase0 as Types)[typeName]; - it(`${fork} - ${typeName} type exists`, function () { + it(`${fork} - ${typeName} type exists`, () => { expect(sszType).toEqualWithMessage(expect.any(Type), `SSZ type ${typeName} for fork ${fork} is not defined`); }); @@ -65,7 +65,7 @@ const sszStatic = for (const testCase of fs.readdirSync(testSuiteDirpath)) { // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts - it(testCase, function () { + it(testCase, () => { // Mainnet must deal with big full states and hash each one multiple times if (ACTIVE_PRESET === "mainnet") { vi.setConfig({testTimeout: 30 * 1000}); @@ -78,7 +78,6 @@ const sszStatic = }; specTestIterator(path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), { - // eslint-disable-next-line @typescript-eslint/naming-convention ssz_static: { type: RunnerType.custom, // starting from v1.4.0-beta.6, there is "whisk" fork in ssz_static tests but we ignore them diff --git a/packages/beacon-node/test/spec/presets/transition.test.ts b/packages/beacon-node/test/spec/presets/transition.test.ts index cae7c667b590..76ad772f8dfb 100644 --- a/packages/beacon-node/test/spec/presets/transition.test.ts +++ b/packages/beacon-node/test/spec/presets/transition.test.ts @@ -78,18 +78,16 @@ const transition = shouldError: (testCase) => testCase.post === undefined, timeout: 10000, getExpected: (testCase) => testCase.post, - expectFunc: (testCase, expected, actual) => { + expectFunc: (_testCase, expected, actual) => { expectEqualBeaconState(forkNext, expected, actual); }, // Do not manually skip tests here, do it in packages/beacon-node/test/spec/presets/index.test.ts shouldSkip: (_testcase, name, _index) => - skipTestNames !== undefined && skipTestNames.some((skipTestName) => name.includes(skipTestName)), + skipTestNames?.some((skipTestName) => name.includes(skipTestName)) ?? false, }, }; }; -/* eslint-disable @typescript-eslint/naming-convention */ - function getTransitionConfig(fork: ForkName, forkEpoch: number): Partial { switch (fork) { case ForkName.phase0: diff --git a/packages/beacon-node/test/spec/specTestVersioning.ts b/packages/beacon-node/test/spec/specTestVersioning.ts index 7229a0236d84..89c701a83514 100644 --- a/packages/beacon-node/test/spec/specTestVersioning.ts +++ b/packages/beacon-node/test/spec/specTestVersioning.ts @@ -11,11 +11,10 @@ import {DownloadTestsOptions} from "@lodestar/spec-test-util/downloadTests"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const ethereumConsensusSpecsTests: DownloadTestsOptions = { - specVersion: "v1.5.0-alpha.5", + specVersion: "v1.5.0-alpha.8", // Target directory is the host package root: 'packages/*/spec-tests' outputDir: path.join(__dirname, "../../spec-tests"), specTestsRepoUrl: "https://github.com/ethereum/consensus-spec-tests", diff --git a/packages/beacon-node/test/spec/utils/runValidSszTest.ts b/packages/beacon-node/test/spec/utils/runValidSszTest.ts index e5f249ab5257..a8d3060af08d 100644 --- a/packages/beacon-node/test/spec/utils/runValidSszTest.ts +++ b/packages/beacon-node/test/spec/utils/runValidSszTest.ts @@ -21,7 +21,7 @@ export function runValidSszTest(type: Type, testData: ValidTestCaseData console.log( JSON.stringify( testData.jsonValue, - (key, value: unknown) => (typeof value === "bigint" ? value.toString() : value), + (_key, value: unknown) => (typeof value === "bigint" ? value.toString() : value), 2 ) ); @@ -168,9 +168,8 @@ function wrapErr(fn: () => T, prefix: string): T { export function toJsonOrString(value: unknown): unknown { if (typeof value === "number" || typeof value === "bigint") { return value.toString(10); - } else { - return value; } + return value; } function renderTree(node: Node): void { diff --git a/packages/beacon-node/test/spec/utils/specTestIterator.ts b/packages/beacon-node/test/spec/utils/specTestIterator.ts index 48a002580043..d8b4f9c0574c 100644 --- a/packages/beacon-node/test/spec/utils/specTestIterator.ts +++ b/packages/beacon-node/test/spec/utils/specTestIterator.ts @@ -65,10 +65,9 @@ export const defaultSkipOpts: SkipOpts = { skippedTestSuites: [ /^capella\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, /^deneb\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, - /^electra\/light_client\/.*/, + /^electra\/light_client\/single_merkle_proof\/BeaconBlockBody.*/, ], - // TODO Electra: Review this test in the next spec test release - skippedTests: [/^deneb\/light_client\/sync\/.*electra_fork.*/], + skippedTests: [], skippedRunners: ["merkle_proof", "networking"], }; diff --git a/packages/beacon-node/test/spec/utils/types.ts b/packages/beacon-node/test/spec/utils/types.ts index 94ad7b73de3f..00bebffd7d7e 100644 --- a/packages/beacon-node/test/spec/utils/types.ts +++ b/packages/beacon-node/test/spec/utils/types.ts @@ -26,8 +26,6 @@ export type TestRunner = | {type: RunnerType.default; fn: TestRunnerFn} | {type: RunnerType.custom; fn: TestRunnerCustom}; -/* eslint-disable @typescript-eslint/naming-convention */ - export type BaseSpecTest = { meta?: { bls_setting?: bigint; diff --git a/packages/beacon-node/test/tsconfig.json b/packages/beacon-node/test/tsconfig.json index 7e6bad81b22f..f4241fc1fbcd 100644 --- a/packages/beacon-node/test/tsconfig.json +++ b/packages/beacon-node/test/tsconfig.json @@ -3,4 +3,4 @@ "compilerOptions": { "noEmit": false } -} \ No newline at end of file +} diff --git a/packages/beacon-node/test/unit-mainnet/network/gossip/scoringParameters.test.ts b/packages/beacon-node/test/unit-mainnet/network/gossip/scoringParameters.test.ts index 7ef09af2cd89..b3137755857f 100644 --- a/packages/beacon-node/test/unit-mainnet/network/gossip/scoringParameters.test.ts +++ b/packages/beacon-node/test/unit-mainnet/network/gossip/scoringParameters.test.ts @@ -12,7 +12,7 @@ import {ZERO_HASH} from "../../../../src/constants/index.js"; * Refer to Teku tests at * https://github.com/ConsenSys/teku/blob/e18ab9903442410aa04b590c4cc46734e13d3ffd/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/config/GossipScoringConfiguratorTest.java#L38 */ -describe("computeGossipPeerScoreParams", function () { +describe("computeGossipPeerScoreParams", () => { const config = createBeaconConfig(mainnetChainConfig, ZERO_HASH); // Cheap stub on new BeaconConfig instance config.forkName2ForkDigest = () => Buffer.alloc(4, 1); 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 a8eaffa42005..99bac5de7ef4 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 @@ -4,17 +4,17 @@ import {ApiTestModules, getApiTestModules} from "../../../../utils/api.js"; import {getBeaconApi} from "../../../../../src/api/impl/beacon/index.js"; import {Mutable} from "../../../../utils/types.js"; -describe("beacon api implementation", function () { +describe("beacon api implementation", () => { let modules: ApiTestModules; let api: ReturnType; - beforeAll(function () { + beforeAll(() => { modules = getApiTestModules(); api = getBeaconApi(modules); }); - describe("getGenesis", function () { - it("success", async function () { + describe("getGenesis", () => { + it("success", async () => { (modules.chain as Mutable).genesisTime = 0; (modules.chain as Mutable).genesisValidatorsRoot = Buffer.alloc(32); 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 9b3c960ff2ec..5e4e8a31ec7f 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 @@ -7,12 +7,12 @@ import {ApiTestModules, getApiTestModules} from "../../../../../utils/api.js"; import {generateProtoBlock, generateSignedBlockAtSlot} from "../../../../../utils/typeGenerator.js"; import {getBeaconBlockApi} from "../../../../../../src/api/impl/beacon/blocks/index.js"; -describe("api - beacon - getBlockHeaders", function () { +describe("api - beacon - getBlockHeaders", () => { let modules: ApiTestModules; let api: ReturnType; const parentRoot = toHexString(Buffer.alloc(32, 1)); - beforeEach(function () { + beforeEach(() => { modules = getApiTestModules(); api = getBeaconBlockApi(modules); @@ -24,7 +24,7 @@ describe("api - beacon - getBlockHeaders", function () { vi.clearAllMocks(); }); - it.skip("no filters - assume head slot", async function () { + it.skip("no filters - assume head slot", async () => { modules.forkChoice.getHead.mockReturnValue(generateProtoBlock({slot: 1})); when(modules.chain.getCanonicalBlockAtSlot) .calledWith(1) @@ -55,13 +55,13 @@ describe("api - beacon - getBlockHeaders", function () { expect(modules.db.block.get).toHaveBeenCalledTimes(1); }); - it("future slot", async function () { + it("future slot", async () => { modules.forkChoice.getHead.mockReturnValue(generateProtoBlock({slot: 1})); const {data: blockHeaders} = await api.getBlockHeaders({slot: 2}); expect(blockHeaders.length).toBe(0); }); - it("finalized slot", async function () { + it("finalized slot", async () => { modules.forkChoice.getHead.mockReturnValue(generateProtoBlock({slot: 2})); when(modules.chain.getCanonicalBlockAtSlot) .calledWith(0) @@ -72,14 +72,14 @@ describe("api - beacon - getBlockHeaders", function () { expect(blockHeaders[0].canonical).toBe(true); }); - it("skip slot", async function () { + it("skip slot", async () => { modules.forkChoice.getHead.mockReturnValue(generateProtoBlock({slot: 2})); when(modules.chain.getCanonicalBlockAtSlot).calledWith(0).thenResolve(null); const {data: blockHeaders} = await api.getBlockHeaders({slot: 0}); expect(blockHeaders.length).toBe(0); }); - it.skip("parent root filter - both finalized and non finalized results", async function () { + it.skip("parent root filter - both finalized and non finalized results", async () => { modules.db.blockArchive.getByParentRoot.mockResolvedValue(ssz.phase0.SignedBeaconBlock.defaultValue()); modules.forkChoice.getBlockSummariesByParentRoot.mockReturnValue([ generateProtoBlock({slot: 2}), @@ -99,7 +99,7 @@ describe("api - beacon - getBlockHeaders", function () { expect(blockHeaders.filter((b) => b.canonical).length).toBe(2); }); - it("parent root - no finalized block", async function () { + it("parent root - no finalized block", async () => { modules.db.blockArchive.getByParentRoot.mockResolvedValue(null); modules.forkChoice.getBlockSummariesByParentRoot.mockReturnValue([generateProtoBlock({slot: 1})]); when(modules.forkChoice.getCanonicalBlockAtSlot).calledWith(1).thenReturn(generateProtoBlock()); @@ -109,14 +109,14 @@ describe("api - beacon - getBlockHeaders", function () { expect(blockHeaders.length).toBe(1); }); - it("parent root - no non finalized blocks", async function () { + it("parent root - no non finalized blocks", async () => { modules.db.blockArchive.getByParentRoot.mockResolvedValue(ssz.phase0.SignedBeaconBlock.defaultValue()); modules.forkChoice.getBlockSummariesByParentRoot.mockReturnValue([]); const {data: blockHeaders} = await api.getBlockHeaders({parentRoot}); expect(blockHeaders.length).toBe(1); }); - it("parent root + slot filter", async function () { + it("parent root + slot filter", async () => { modules.db.blockArchive.getByParentRoot.mockResolvedValue(ssz.phase0.SignedBeaconBlock.defaultValue()); modules.forkChoice.getBlockSummariesByParentRoot.mockReturnValue([ generateProtoBlock({slot: 2}), diff --git a/packages/beacon-node/test/unit/api/impl/beacon/state/utils.test.ts b/packages/beacon-node/test/unit/api/impl/beacon/state/utils.test.ts index a6020c0a3c13..958093ffba6d 100644 --- a/packages/beacon-node/test/unit/api/impl/beacon/state/utils.test.ts +++ b/packages/beacon-node/test/unit/api/impl/beacon/state/utils.test.ts @@ -1,107 +1,9 @@ import {describe, it, expect} from "vitest"; import {toHexString} from "@chainsafe/ssz"; -import {phase0} from "@lodestar/types"; -import {getValidatorStatus, getStateValidatorIndex} from "../../../../../../src/api/impl/beacon/state/utils.js"; +import {getStateValidatorIndex} from "../../../../../../src/api/impl/beacon/state/utils.js"; import {generateCachedAltairState} from "../../../../../utils/state.js"; -describe("beacon state api utils", function () { - describe("getValidatorStatus", function () { - it("should return PENDING_INITIALIZED", function () { - const validator = { - activationEpoch: 1, - activationEligibilityEpoch: Infinity, - } as phase0.Validator; - const currentEpoch = 0; - const status = getValidatorStatus(validator, currentEpoch); - expect(status).toBe("pending_initialized"); - }); - it("should return PENDING_QUEUED", function () { - const validator = { - activationEpoch: 1, - activationEligibilityEpoch: 101010101101010, - } as phase0.Validator; - const currentEpoch = 0; - const status = getValidatorStatus(validator, currentEpoch); - expect(status).toBe("pending_queued"); - }); - it("should return ACTIVE_ONGOING", function () { - const validator = { - activationEpoch: 1, - exitEpoch: Infinity, - } as phase0.Validator; - const currentEpoch = 1; - const status = getValidatorStatus(validator, currentEpoch); - expect(status).toBe("active_ongoing"); - }); - it("should return ACTIVE_SLASHED", function () { - const validator = { - activationEpoch: 1, - exitEpoch: 101010101101010, - slashed: true, - } as phase0.Validator; - const currentEpoch = 1; - const status = getValidatorStatus(validator, currentEpoch); - expect(status).toBe("active_slashed"); - }); - it("should return ACTIVE_EXITING", function () { - const validator = { - activationEpoch: 1, - exitEpoch: 101010101101010, - slashed: false, - } as phase0.Validator; - const currentEpoch = 1; - const status = getValidatorStatus(validator, currentEpoch); - expect(status).toBe("active_exiting"); - }); - it("should return EXITED_SLASHED", function () { - const validator = { - exitEpoch: 1, - withdrawableEpoch: 3, - slashed: true, - } as phase0.Validator; - const currentEpoch = 2; - const status = getValidatorStatus(validator, currentEpoch); - expect(status).toBe("exited_slashed"); - }); - it("should return EXITED_UNSLASHED", function () { - const validator = { - exitEpoch: 1, - withdrawableEpoch: 3, - slashed: false, - } as phase0.Validator; - const currentEpoch = 2; - const status = getValidatorStatus(validator, currentEpoch); - expect(status).toBe("exited_unslashed"); - }); - it("should return WITHDRAWAL_POSSIBLE", function () { - const validator = { - withdrawableEpoch: 1, - effectiveBalance: 32, - } as phase0.Validator; - const currentEpoch = 1; - const status = getValidatorStatus(validator, currentEpoch); - expect(status).toBe("withdrawal_possible"); - }); - it("should return WITHDRAWAL_DONE", function () { - const validator = { - withdrawableEpoch: 1, - effectiveBalance: 0, - } as phase0.Validator; - const currentEpoch = 1; - const status = getValidatorStatus(validator, currentEpoch); - expect(status).toBe("withdrawal_done"); - }); - it("should error", function () { - const validator = {} as phase0.Validator; - const currentEpoch = 0; - try { - getValidatorStatus(validator, currentEpoch); - } catch (error) { - expect(error).toHaveProperty("message", "ValidatorStatus unknown"); - } - }); - }); - +describe("beacon state api utils", () => { describe("getStateValidatorIndex", () => { const state = generateCachedAltairState(); const pubkey2index = state.epochCtx.pubkey2index; @@ -117,7 +19,13 @@ describe("beacon state api utils", function () { // "validator id not in state" expect(getStateValidatorIndex(String(state.validators.length), state, pubkey2index).valid).toBe(false); // "validator pubkey not in state" - expect(getStateValidatorIndex("0xabcd", state, pubkey2index).valid).toBe(false); + expect( + getStateValidatorIndex( + "0xa99af0913a2834ef4959637e8d7c4e17f0b63adc587d36ab43510452db3102d0771a4554ea4118a33913827d5ee80b76", + state, + pubkey2index + ).valid + ).toBe(false); }); it("should return valid: true on validator indices / pubkeys in the state", () => { 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 d6954f632d5e..7d0adebbea89 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 @@ -3,34 +3,34 @@ import {routes} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {getConfigApi, renderJsonSpec} from "../../../../../src/api/impl/config/index.js"; -describe("config api implementation", function () { +describe("config api implementation", () => { let api: ReturnType; - beforeEach(function () { + beforeEach(() => { api = getConfigApi({config}); }); - describe("getForkSchedule", function () { - it("should get known scheduled forks", async function () { + describe("getForkSchedule", () => { + it("should get known scheduled forks", async () => { const {data: forkSchedule} = await api.getForkSchedule(); expect(forkSchedule.length).toBe(Object.keys(config.forks).length); }); }); - describe("getDepositContract", function () { - it("should get the deposit contract from config", async function () { + describe("getDepositContract", () => { + it("should get the deposit contract from config", async () => { 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); }); }); - describe("getSpec", function () { + describe("getSpec", () => { it("Ensure spec can be rendered", () => { renderJsonSpec(config); }); - it("should get the spec", async function () { + it("should get the spec", async () => { const {data: specJson} = (await api.getSpec()) as {data: routes.config.Spec}; expect(specJson.SECONDS_PER_ETH1_BLOCK).toBe("14"); 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 e031c3ac9958..5b1686d42f57 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 @@ -11,7 +11,6 @@ vi.mock("../../../../../src/chain/index.js", async (importActual) => { return { ...mod, - // eslint-disable-next-line @typescript-eslint/naming-convention BeaconChain: vi.spyOn(mod, "BeaconChain").mockImplementation(() => { return { emitter: new ChainEventEmitter(), @@ -23,22 +22,20 @@ vi.mock("../../../../../src/chain/index.js", async (importActual) => { }; }); -describe("Events api impl", function () { - describe("beacon event stream", function () { +describe("Events api impl", () => { + describe("beacon event stream", () => { let chainStub: MockedObject; let chainEventEmmitter: ChainEventEmitter; let api: ReturnType; + let controller: AbortController; - beforeEach(function () { + beforeEach(() => { chainStub = vi.mocked(new BeaconChain({} as any, {} as any), {partial: true, deep: false}); chainEventEmmitter = chainStub.emitter; api = getEventsApi({config, chain: chainStub}); - }); - - let controller: AbortController; - beforeEach(() => { controller = new AbortController(); }); + afterEach(() => controller.abort()); function getEvents(topics: routes.events.EventType[]): routes.events.BeaconEvent[] { @@ -63,7 +60,7 @@ describe("Events api impl", function () { executionOptimistic: false, }; - it("should ignore not sent topics", async function () { + it("should ignore not sent topics", async () => { const events = getEvents([routes.events.EventType.head]); chainEventEmmitter.emit(routes.events.EventType.attestation, ssz.phase0.Attestation.defaultValue()); 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 b101382e01a0..b954f983adcc 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 @@ -3,6 +3,7 @@ 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"; +import {Slot} from "@lodestar/types"; import {ApiTestModules, getApiTestModules} from "../../../../../utils/api.js"; import {FAR_FUTURE_EPOCH} from "../../../../../../src/constants/index.js"; import {SYNC_TOLERANCE_EPOCHS, getValidatorApi} from "../../../../../../src/api/impl/validator/index.js"; @@ -12,20 +13,35 @@ import {createCachedBeaconStateTest} from "../../../../../utils/cachedBeaconStat import {SyncState} from "../../../../../../src/sync/interface.js"; import {defaultApiOptions} from "../../../../../../src/api/options.js"; -describe("get proposers api impl", function () { +describe("get proposers api impl", () => { + const currentEpoch = 2; + const currentSlot = SLOTS_PER_EPOCH * currentEpoch; + let api: ReturnType; let modules: ApiTestModules; let state: BeaconStateAllForks; let cachedState: ReturnType; - beforeEach(function () { + beforeEach(() => { vi.useFakeTimers({now: 0}); + vi.advanceTimersByTime(currentSlot * config.SECONDS_PER_SLOT * 1000); modules = getApiTestModules({clock: "real"}); api = getValidatorApi(defaultApiOptions, modules); + initializeState(currentSlot); + + modules.chain.getHeadStateAtCurrentEpoch.mockResolvedValue(cachedState); + modules.forkChoice.getHead.mockReturnValue(zeroProtoBlock); + modules.forkChoice.getFinalizedBlock.mockReturnValue(zeroProtoBlock); + modules.db.block.get.mockResolvedValue({message: {stateRoot: Buffer.alloc(32)}} as any); + + vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.Synced); + }); + + function initializeState(slot: Slot): void { state = generateState( { - slot: 0, + slot, validators: generateValidators(25, { effectiveBalance: MAX_EFFECTIVE_BALANCE, activationEpoch: 0, @@ -37,14 +53,10 @@ describe("get proposers api impl", function () { ); cachedState = createCachedBeaconStateTest(state, config); - modules.chain.getHeadStateAtCurrentEpoch.mockResolvedValue(cachedState); - modules.forkChoice.getHead.mockReturnValue(zeroProtoBlock); - modules.db.block.get.mockResolvedValue({message: {stateRoot: Buffer.alloc(32)}} as any); - - vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.Synced); vi.spyOn(cachedState.epochCtx, "getBeaconProposersNextEpoch"); vi.spyOn(cachedState.epochCtx, "getBeaconProposers"); - }); + vi.spyOn(cachedState.epochCtx, "getBeaconProposersPrevEpoch"); + } afterEach(() => { vi.useRealTimers(); @@ -54,7 +66,7 @@ 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({epoch: 1})).rejects.toThrow("Node is syncing - headSlot 0 currentSlot 9"); + await expect(api.getProposerDuties({epoch: 1})).rejects.toThrow("Node is syncing - headSlot 0 currentSlot 25"); }); it("should raise error if node stalled", async () => { @@ -65,34 +77,61 @@ describe("get proposers api impl", function () { }); it("should get proposers for current epoch", async () => { - const {data: result} = (await api.getProposerDuties({epoch: 0})) as {data: routes.validator.ProposerDutyList}; + const {data: result} = (await api.getProposerDuties({epoch: currentEpoch})) as { + data: routes.validator.ProposerDutyList; + }; expect(result.length).toBe(SLOTS_PER_EPOCH); expect(cachedState.epochCtx.getBeaconProposers).toHaveBeenCalledOnce(); expect(cachedState.epochCtx.getBeaconProposersNextEpoch).not.toHaveBeenCalled(); - expect(result.map((p) => p.slot)).toEqual(Array.from({length: SLOTS_PER_EPOCH}, (_, i) => i)); + expect(cachedState.epochCtx.getBeaconProposersPrevEpoch).not.toHaveBeenCalled(); + expect(result.map((p) => p.slot)).toEqual( + Array.from({length: SLOTS_PER_EPOCH}, (_, i) => currentEpoch * SLOTS_PER_EPOCH + i) + ); }); it("should get proposers for next epoch", async () => { - const {data: result} = (await api.getProposerDuties({epoch: 1})) as {data: routes.validator.ProposerDutyList}; + const nextEpoch = currentEpoch + 1; + const {data: result} = (await api.getProposerDuties({epoch: nextEpoch})) as { + data: routes.validator.ProposerDutyList; + }; expect(result.length).toBe(SLOTS_PER_EPOCH); expect(cachedState.epochCtx.getBeaconProposers).not.toHaveBeenCalled(); expect(cachedState.epochCtx.getBeaconProposersNextEpoch).toHaveBeenCalledOnce(); - expect(result.map((p) => p.slot)).toEqual(Array.from({length: SLOTS_PER_EPOCH}, (_, i) => SLOTS_PER_EPOCH + i)); + expect(cachedState.epochCtx.getBeaconProposersPrevEpoch).not.toHaveBeenCalled(); + expect(result.map((p) => p.slot)).toEqual( + Array.from({length: SLOTS_PER_EPOCH}, (_, i) => nextEpoch * SLOTS_PER_EPOCH + i) + ); + }); + + it("should get proposers for historical epoch", async () => { + const historicalEpoch = currentEpoch - 2; + initializeState(currentSlot - 2 * SLOTS_PER_EPOCH); + modules.chain.getStateBySlot.mockResolvedValue({state, executionOptimistic: false, finalized: true}); + + const {data: result} = (await api.getProposerDuties({epoch: historicalEpoch})) as { + data: routes.validator.ProposerDutyList; + }; + + expect(result.length).toBe(SLOTS_PER_EPOCH); + // Spy won't be called as `getProposerDuties` will create a new cached beacon state + expect(result.map((p) => p.slot)).toEqual( + Array.from({length: SLOTS_PER_EPOCH}, (_, i) => historicalEpoch * SLOTS_PER_EPOCH + i) + ); }); it("should raise error for more than one epoch in the future", async () => { - await expect(api.getProposerDuties({epoch: 2})).rejects.toThrow( - "Requested epoch 2 must equal current 0 or next epoch 1" + await expect(api.getProposerDuties({epoch: currentEpoch + 2})).rejects.toThrow( + "Requested epoch 4 must not be more than one epoch in the future" ); }); it("should have different proposer validator public keys for current and next epoch", async () => { - const {data: currentProposers} = (await api.getProposerDuties({epoch: 0})) as { + const {data: currentProposers} = (await api.getProposerDuties({epoch: currentEpoch})) as { data: routes.validator.ProposerDutyList; }; - const {data: nextProposers} = (await api.getProposerDuties({epoch: 1})) as { + const {data: nextProposers} = (await api.getProposerDuties({epoch: currentEpoch + 1})) as { data: routes.validator.ProposerDutyList; }; @@ -101,10 +140,10 @@ describe("get proposers api impl", function () { }); it("should have different proposer validator indexes for current and next epoch", async () => { - const {data: currentProposers} = (await api.getProposerDuties({epoch: 0})) as { + const {data: currentProposers} = (await api.getProposerDuties({epoch: currentEpoch})) as { data: routes.validator.ProposerDutyList; }; - const {data: nextProposers} = (await api.getProposerDuties({epoch: 1})) as { + const {data: nextProposers} = (await api.getProposerDuties({epoch: currentEpoch + 1})) as { data: routes.validator.ProposerDutyList; }; @@ -112,10 +151,10 @@ describe("get proposers api impl", function () { }); it("should have different proposer slots for current and next epoch", async () => { - const {data: currentProposers} = (await api.getProposerDuties({epoch: 0})) as { + const {data: currentProposers} = (await api.getProposerDuties({epoch: currentEpoch})) as { data: routes.validator.ProposerDutyList; }; - const {data: nextProposers} = (await api.getProposerDuties({epoch: 1})) as { + const {data: nextProposers} = (await api.getProposerDuties({epoch: currentEpoch + 1})) as { data: routes.validator.ProposerDutyList; }; 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 84872ca6045c..fdbfec5ac503 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 @@ -5,16 +5,16 @@ import {ApiTestModules, getApiTestModules} from "../../../../utils/api.js"; import {getValidatorApi} from "../../../../../src/api/impl/validator/index.js"; import {defaultApiOptions} from "../../../../../src/api/options.js"; -describe("api - validator - produceAttestationData", function () { +describe("api - validator - produceAttestationData", () => { let modules: ApiTestModules; let api: ReturnType; - beforeEach(function () { + beforeEach(() => { modules = getApiTestModules(); api = getValidatorApi(defaultApiOptions, modules); }); - it("Should throw when node is not synced", async function () { + it("Should throw when node is not synced", async () => { // Set the node's state to way back from current slot const currentSlot = 100000; const headSlot = 0; @@ -25,7 +25,7 @@ describe("api - validator - produceAttestationData", function () { await expect(api.produceAttestationData({committeeIndex: 0, slot: 0})).rejects.toThrow("Node is syncing"); }); - it("Should throw error when node is stopped", async function () { + it("Should throw error when node is stopped", async () => { const currentSlot = 100000; vi.spyOn(modules.chain.clock, "currentSlot", "get").mockReturnValue(currentSlot); vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.Stalled); 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 a23373938f64..306b18481c1f 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 @@ -16,7 +16,7 @@ import {generateProtoBlock} from "../../../../utils/typeGenerator.js"; import {ZERO_HASH_HEX} from "../../../../../src/constants/index.js"; import {defaultApiOptions} from "../../../../../src/api/options.js"; -describe("api/validator - produceBlockV2", function () { +describe("api/validator - produceBlockV2", () => { let api: ReturnType; let modules: ApiTestModules; let state: CachedBeaconStateBellatrix; @@ -32,7 +32,7 @@ describe("api/validator - produceBlockV2", function () { vi.clearAllMocks(); }); - it("correctly pass feeRecipient to produceBlock", async function () { + it("correctly pass feeRecipient to produceBlock", async () => { const fullBlock = ssz.bellatrix.BeaconBlock.defaultValue(); const executionPayloadValue = ssz.Wei.defaultValue(); const consensusBlockValue = ssz.Wei.defaultValue(); 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 a7299aa3b956..f705e4b38e14 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 @@ -12,8 +12,7 @@ import {CommonBlockBody} from "../../../../../src/chain/interface.js"; import {zeroProtoBlock} from "../../../../utils/state.js"; import {defaultApiOptions} from "../../../../../src/api/options.js"; -/* eslint-disable @typescript-eslint/naming-convention */ -describe("api/validator - produceBlockV3", function () { +describe("api/validator - produceBlockV3", () => { let modules: ApiTestModules; let api: ReturnType; @@ -65,102 +64,100 @@ describe("api/validator - produceBlockV3", function () { [routes.validator.BuilderSelection.ExecutionOnly, 1, 1, 1, true, "engine"], ]; - testCases.forEach( - ([ - builderSelection, - builderPayloadValue, - enginePayloadValue, - consensusBlockValue, - shouldOverrideBuilder, - finalSelection, - ]) => { - it(`produceBlockV3 - ${finalSelection} produces block`, async () => { - const fullBlock = ssz.bellatrix.BeaconBlock.defaultValue(); - const blindedBlock = ssz.bellatrix.BlindedBeaconBlock.defaultValue(); - - const slot = 1 * SLOTS_PER_EPOCH; - const randaoReveal = fullBlock.body.randaoReveal; - const graffiti = "a".repeat(32); - const feeRecipient = "0xccccccccccccccccccccccccccccccccccccccaa"; - const currentSlot = 1 * SLOTS_PER_EPOCH; - - vi.spyOn(modules.chain.clock, "currentSlot", "get").mockReturnValue(currentSlot); - vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.Synced); - modules.chain.recomputeForkChoiceHead.mockReturnValue({ - 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 = { - attestations: fullBlock.body.attestations, - attesterSlashings: fullBlock.body.attesterSlashings, - deposits: fullBlock.body.deposits, - proposerSlashings: fullBlock.body.proposerSlashings, - eth1Data: fullBlock.body.eth1Data, - graffiti: fullBlock.body.graffiti, - randaoReveal: fullBlock.body.randaoReveal, - voluntaryExits: fullBlock.body.voluntaryExits, - blsToExecutionChanges: [], - syncAggregate: fullBlock.body.syncAggregate, - }; - - modules.chain.produceCommonBlockBody.mockResolvedValue(commonBlockBody); - - modules.chain.produceBlock.mockResolvedValue({ - block: fullBlock, - executionPayloadValue: BigInt(enginePayloadValue), - consensusBlockValue: BigInt(consensusBlockValue), - shouldOverrideBuilder, - }); - } else { - modules.chain.produceBlock.mockRejectedValue(Error("not produced")); - } - - if (builderPayloadValue !== null) { - modules.chain.produceBlindedBlock.mockResolvedValue({ - block: blindedBlock, - executionPayloadValue: BigInt(builderPayloadValue), - consensusBlockValue: BigInt(consensusBlockValue), - }); - } else { - modules.chain.produceBlindedBlock.mockRejectedValue(Error("not produced")); - } - const _skipRandaoVerification = false; - const produceBlockOpts = { - strictFeeRecipientCheck: false, - builderSelection, - feeRecipient, + for (const [ + builderSelection, + builderPayloadValue, + enginePayloadValue, + consensusBlockValue, + shouldOverrideBuilder, + finalSelection, + ] of testCases) { + it(`produceBlockV3 - ${finalSelection} produces block`, async () => { + const fullBlock = ssz.bellatrix.BeaconBlock.defaultValue(); + const blindedBlock = ssz.bellatrix.BlindedBeaconBlock.defaultValue(); + + const slot = 1 * SLOTS_PER_EPOCH; + const randaoReveal = fullBlock.body.randaoReveal; + const graffiti = "a".repeat(32); + const feeRecipient = "0xccccccccccccccccccccccccccccccccccccccaa"; + const currentSlot = 1 * SLOTS_PER_EPOCH; + + vi.spyOn(modules.chain.clock, "currentSlot", "get").mockReturnValue(currentSlot); + vi.spyOn(modules.sync, "state", "get").mockReturnValue(SyncState.Synced); + modules.chain.recomputeForkChoiceHead.mockReturnValue({ + 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 = { + attestations: fullBlock.body.attestations, + attesterSlashings: fullBlock.body.attesterSlashings, + deposits: fullBlock.body.deposits, + proposerSlashings: fullBlock.body.proposerSlashings, + eth1Data: fullBlock.body.eth1Data, + graffiti: fullBlock.body.graffiti, + randaoReveal: fullBlock.body.randaoReveal, + voluntaryExits: fullBlock.body.voluntaryExits, + blsToExecutionChanges: [], + syncAggregate: fullBlock.body.syncAggregate, }; - 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).toEqual(expectedBlock); - expect(meta.executionPayloadBlinded).toEqual(expectedExecution); - - // check call counts - if (builderSelection === routes.validator.BuilderSelection.ExecutionOnly) { - expect(modules.chain.produceBlindedBlock).toBeCalledTimes(0); - } else { - expect(modules.chain.produceBlindedBlock).toBeCalledTimes(1); - } + modules.chain.produceCommonBlockBody.mockResolvedValue(commonBlockBody); - if (builderSelection === routes.validator.BuilderSelection.BuilderOnly) { - expect(modules.chain.produceBlock).toBeCalledTimes(0); - } else { - expect(modules.chain.produceBlock).toBeCalledTimes(1); - } + modules.chain.produceBlock.mockResolvedValue({ + block: fullBlock, + executionPayloadValue: BigInt(enginePayloadValue), + consensusBlockValue: BigInt(consensusBlockValue), + shouldOverrideBuilder, + }); + } else { + modules.chain.produceBlock.mockRejectedValue(Error("not produced")); + } + + if (builderPayloadValue !== null) { + modules.chain.produceBlindedBlock.mockResolvedValue({ + block: blindedBlock, + executionPayloadValue: BigInt(builderPayloadValue), + consensusBlockValue: BigInt(consensusBlockValue), + }); + } else { + modules.chain.produceBlindedBlock.mockRejectedValue(Error("not produced")); + } + const _skipRandaoVerification = false; + const produceBlockOpts = { + strictFeeRecipientCheck: false, + builderSelection, + feeRecipient, + }; + + const {data: block, meta} = await api.produceBlockV3({ + slot, + randaoReveal, + graffiti, + skipRandaoVerification: _skipRandaoVerification, + ...produceBlockOpts, }); - } - ); + + const expectedBlock = finalSelection === "builder" ? blindedBlock : fullBlock; + const expectedExecution = finalSelection === "builder"; + + expect(block).toEqual(expectedBlock); + expect(meta.executionPayloadBlinded).toEqual(expectedExecution); + + // check call counts + if (builderSelection === routes.validator.BuilderSelection.ExecutionOnly) { + expect(modules.chain.produceBlindedBlock).toBeCalledTimes(0); + } else { + expect(modules.chain.produceBlindedBlock).toBeCalledTimes(1); + } + + if (builderSelection === routes.validator.BuilderSelection.BuilderOnly) { + expect(modules.chain.produceBlock).toBeCalledTimes(0); + } else { + expect(modules.chain.produceBlock).toBeCalledTimes(1); + } + }); + } }); diff --git a/packages/beacon-node/test/unit/chain/archive/blockArchiver.test.ts b/packages/beacon-node/test/unit/chain/archive/blockArchiver.test.ts index d9c3b93a76ee..dc7c9bb75291 100644 --- a/packages/beacon-node/test/unit/chain/archive/blockArchiver.test.ts +++ b/packages/beacon-node/test/unit/chain/archive/blockArchiver.test.ts @@ -9,14 +9,14 @@ import {archiveBlocks} from "../../../../src/chain/archiver/archiveBlocks.js"; import {MockedBeaconDb, getMockedBeaconDb} from "../../../mocks/mockedBeaconDb.js"; import {MockedBeaconChain, getMockedBeaconChain} from "../../../mocks/mockedBeaconChain.js"; -describe("block archiver task", function () { +describe("block archiver task", () => { const logger = testLogger(); let dbStub: MockedBeaconDb; let forkChoiceStub: MockedBeaconChain["forkChoice"]; let lightclientServer: MockedBeaconChain["lightClientServer"]; - beforeEach(function () { + beforeEach(() => { const chain = getMockedBeaconChain(); dbStub = getMockedBeaconDb(); forkChoiceStub = chain.forkChoice; @@ -30,7 +30,7 @@ describe("block archiver task", function () { vi.clearAllMocks(); }); - it("should archive finalized blocks", async function () { + it("should archive finalized blocks", async () => { const blockBytes = ssz.phase0.SignedBeaconBlock.serialize(ssz.phase0.SignedBeaconBlock.defaultValue()); vi.spyOn(dbStub.block, "getBinary").mockResolvedValue(Buffer.from(blockBytes)); // block i has slot i+1 diff --git a/packages/beacon-node/test/unit/chain/archive/stateArchiver.test.ts b/packages/beacon-node/test/unit/chain/archive/stateArchiver.test.ts index fe21fd64af96..cbfba0a362df 100644 --- a/packages/beacon-node/test/unit/chain/archive/stateArchiver.test.ts +++ b/packages/beacon-node/test/unit/chain/archive/stateArchiver.test.ts @@ -1,6 +1,6 @@ import {describe, it, expect} from "vitest"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; -import {computeStateSlotsToDelete} from "../../../../src/chain/archiver/archiveStates.js"; +import {computeStateSlotsToDelete} from "../../../../src/chain/archiver/strategies/frequencyStateArchiveStrategy.js"; describe("state archiver task", () => { describe("computeStateSlotsToDelete", () => { diff --git a/packages/beacon-node/test/unit/chain/beaconProposerCache.ts b/packages/beacon-node/test/unit/chain/beaconProposerCache.ts index 4545ef0c94b2..b75bbf546a98 100644 --- a/packages/beacon-node/test/unit/chain/beaconProposerCache.ts +++ b/packages/beacon-node/test/unit/chain/beaconProposerCache.ts @@ -1,31 +1,31 @@ -import {expect} from "vitest"; +import {expect, describe, it, beforeEach} from "vitest"; import {BeaconProposerCache} from "../../../src/chain/beaconProposerCache.js"; const suggestedFeeRecipient = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; -describe("BeaconProposerCache", function () { +describe("BeaconProposerCache", () => { let cache: BeaconProposerCache; - beforeEach(function () { + beforeEach(() => { // max 2 items cache = new BeaconProposerCache({suggestedFeeRecipient}); cache.add(1, {validatorIndex: 23, feeRecipient: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}); cache.add(3, {validatorIndex: 43, feeRecipient: "0xcccccccccccccccccccccccccccccccccccccccc"}); }); - it("get default", function () { + it("get default", () => { expect(cache.get(32)).toBe("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); }); - it("get what has been set", function () { + it("get what has been set", () => { expect(cache.get(23)).toBe("0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); }); - it("override and get latest", function () { + it("override and get latest", () => { cache.add(5, {validatorIndex: 23, feeRecipient: "0xdddddddddddddddddddddddddddddddddddddddd"}); expect(cache.get(23)).toBe("0xdddddddddddddddddddddddddddddddddddddddd"); }); - it("prune", function () { + it("prune", () => { cache.prune(4); // Default for what has been pruned 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 a45678e5bf48..1296a79d5eab 100644 --- a/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts +++ b/packages/beacon-node/test/unit/chain/blocks/verifyBlocksSanityChecks.test.ts @@ -13,7 +13,7 @@ import {ClockStopped} from "../../../mocks/clock.js"; import {BlockSource, getBlockInput} from "../../../../src/chain/blocks/types.js"; import {MockedBeaconChain, getMockedBeaconChain} from "../../../mocks/mockedBeaconChain.js"; -describe("chain / blocks / verifyBlocksSanityChecks", function () { +describe("chain / blocks / verifyBlocksSanityChecks", () => { let forkChoice: MockedBeaconChain["forkChoice"]; let clock: ClockStopped; let modules: {forkChoice: IForkChoice; clock: IClock; config: ChainForkConfig}; diff --git a/packages/beacon-node/test/unit/chain/bls/bls.test.ts b/packages/beacon-node/test/unit/chain/bls/bls.test.ts index 4203d7f9768c..137f2c4dd9df 100644 --- a/packages/beacon-node/test/unit/chain/bls/bls.test.ts +++ b/packages/beacon-node/test/unit/chain/bls/bls.test.ts @@ -5,7 +5,7 @@ import {BlsSingleThreadVerifier} from "../../../../src/chain/bls/singleThread.js import {BlsMultiThreadWorkerPool} from "../../../../src/chain/bls/multithread/index.js"; import {testLogger} from "../../../utils/logger.js"; -describe("BlsVerifier ", function () { +describe("BlsVerifier ", () => { // take time for creating thread pool const numKeys = 3; const secretKeys = Array.from({length: numKeys}, (_, i) => SecretKey.fromKeygen(Buffer.alloc(32, i))); 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 6b96a0d1172f..7cb5d23ba296 100644 --- a/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts +++ b/packages/beacon-node/test/unit/chain/forkChoice/forkChoice.test.ts @@ -5,12 +5,13 @@ import {CheckpointWithHex, ExecutionStatus, ForkChoice, DataAvailabilityStatus} import {FAR_FUTURE_EPOCH, MAX_EFFECTIVE_BALANCE} from "@lodestar/params"; import { CachedBeaconStateAllForks, + computeAnchorCheckpoint, computeEpochAtSlot, getEffectiveBalanceIncrementsZeroed, } from "@lodestar/state-transition"; import {phase0, Slot, ssz, ValidatorIndex} from "@lodestar/types"; import {getTemporaryBlockHeader, processSlots} from "@lodestar/state-transition"; -import {ChainEventEmitter, computeAnchorCheckpoint, initializeForkChoice} from "../../../../src/chain/index.js"; +import {ChainEventEmitter, initializeForkChoice} from "../../../../src/chain/index.js"; import {generateSignedBlockAtSlot} from "../../../utils/typeGenerator.js"; import {createCachedBeaconStateTest} from "../../../utils/cachedBeaconState.js"; import {generateState} from "../../../utils/state.js"; @@ -19,7 +20,7 @@ import {generateValidators} from "../../../utils/validator.js"; // We mock this package globally vi.unmock("@lodestar/fork-choice"); -describe("LodestarForkChoice", function () { +describe("LodestarForkChoice", () => { let forkChoice: ForkChoice; const anchorState = createCachedBeaconStateTest( generateState( @@ -70,7 +71,7 @@ describe("LodestarForkChoice", function () { ); }); - describe("forkchoice", function () { + describe("forkchoice", () => { /** * slot 32(checkpoint) - orphaned (36) * \ diff --git a/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts b/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts index f836e3621cc9..40570fcd26e1 100644 --- a/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts +++ b/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {toHexString} from "@chainsafe/ssz"; import {describe, it, expect} from "vitest"; import {PublicKey, SecretKey} from "@chainsafe/blst"; @@ -12,7 +11,7 @@ import {testLogger} from "../../../utils/logger.js"; import {ZERO_HASH_HEX} from "../../../../src/constants/index.js"; import {Eth1ProviderState, EthJsonRpcBlockRaw, IEth1Provider} from "../../../../src/eth1/interface.js"; -describe("genesis builder", function () { +describe("genesis builder", () => { const logger = testLogger(); const schlesiConfig = Object.assign({}, config, { MIN_GENESIS_TIME: 1587755000, 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 abb520bddafb..3c2f5a664c77 100644 --- a/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts +++ b/packages/beacon-node/test/unit/chain/lightclient/upgradeLightClientHeader.test.ts @@ -4,11 +4,10 @@ import {ForkName, ForkSeq} from "@lodestar/params"; import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {upgradeLightClientHeader} from "@lodestar/light-client/spec"; -describe("UpgradeLightClientHeader", function () { +describe("UpgradeLightClientHeader", () => { let lcHeaderByFork: Record; let testSlots: Record; - /* eslint-disable @typescript-eslint/naming-convention */ const chainConfig = createChainForkConfig({ ...defaultChainConfig, ALTAIR_FORK_EPOCH: 1, @@ -21,14 +20,14 @@ describe("UpgradeLightClientHeader", function () { const genesisValidatorsRoot = Buffer.alloc(32, 0xaa); const config = createBeaconConfig(chainConfig, genesisValidatorsRoot); - beforeEach(function () { + beforeEach(() => { lcHeaderByFork = { phase0: ssz.altair.LightClientHeader.defaultValue(), altair: ssz.altair.LightClientHeader.defaultValue(), capella: ssz.capella.LightClientHeader.defaultValue(), bellatrix: ssz.altair.LightClientHeader.defaultValue(), deneb: ssz.deneb.LightClientHeader.defaultValue(), - electra: ssz.electra.LightClientHeader.defaultValue(), + electra: ssz.deneb.LightClientHeader.defaultValue(), }; testSlots = { @@ -46,7 +45,7 @@ describe("UpgradeLightClientHeader", function () { const fromFork = ForkName[ForkSeq[i] as ForkName]; const toFork = ForkName[ForkSeq[j] as ForkName]; - it(`Successful upgrade ${fromFork}=>${toFork}`, function () { + it(`Successful upgrade ${fromFork}=>${toFork}`, () => { lcHeaderByFork[fromFork].beacon.slot = testSlots[fromFork]; lcHeaderByFork[toFork].beacon.slot = testSlots[fromFork]; @@ -61,7 +60,7 @@ describe("UpgradeLightClientHeader", function () { const fromFork = ForkName[ForkSeq[i] as ForkName]; const toFork = ForkName[ForkSeq[j] as ForkName]; - it(`Throw upgrade error ${fromFork}=>${toFork}`, function () { + it(`Throw upgrade error ${fromFork}=>${toFork}`, () => { lcHeaderByFork[fromFork].beacon.slot = testSlots[fromFork]; lcHeaderByFork[toFork].beacon.slot = testSlots[fromFork]; diff --git a/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts b/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts index f00a300bbe4d..8742d7da9147 100644 --- a/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts @@ -34,7 +34,7 @@ const validSignature = fromHexString( "0xb2afb700f6c561ce5e1b4fedaec9d7c06b822d38c720cf588adfda748860a940adf51634b6788f298c552de40183b5a203b2bbe8b7dd147f0bb5bc97080a12efbb631c8888cb31a99cc4706eb3711865b8ea818c10126e4d818b542e9dbf9ae8" ); -describe("AggregatedAttestationPool", function () { +describe("AggregatedAttestationPool", () => { let pool: AggregatedAttestationPool; const fork = ForkName.altair; const config = createChainForkConfig({ @@ -116,7 +116,7 @@ describe("AggregatedAttestationPool", function () { ]; for (const {name, attestingBits, isReturned} of testCases) { - it(name, function () { + it(name, () => { const aggregationBits = new BitArray(new Uint8Array(attestingBits), committeeLength); pool.add( {...attestation, aggregationBits}, @@ -136,7 +136,7 @@ describe("AggregatedAttestationPool", function () { }); } - it("incorrect source", function () { + it("incorrect source", () => { altairState.currentJustifiedCheckpoint.epoch = 1000; // all attesters are not seen const attestingIndices = [2, 3]; @@ -146,7 +146,7 @@ describe("AggregatedAttestationPool", function () { expect(forkchoiceStub.iterateAncestorBlocks).not.toHaveBeenCalledTimes(1); }); - it("incompatible shuffling - incorrect pivot block root", function () { + it("incompatible shuffling - incorrect pivot block root", () => { // all attesters are not seen const attestingIndices = [2, 3]; pool.add(attestation, attDataRootHex, attestingIndices.length, committee); @@ -305,7 +305,7 @@ describe("MatchingDataAttestationGroup.getAttestationsForBlock", () => { } }); -describe("MatchingDataAttestationGroup aggregateInto", function () { +describe("MatchingDataAttestationGroup aggregateInto", () => { const attestationSeed = ssz.phase0.Attestation.defaultValue(); const attestation1 = {...attestationSeed, ...{aggregationBits: BitArray.fromBoolArray([false, true])}}; const attestation2 = {...attestationSeed, ...{aggregationBits: BitArray.fromBoolArray([true, false])}}; @@ -334,7 +334,7 @@ describe("MatchingDataAttestationGroup aggregateInto", function () { }); }); -describe("aggregateConsolidation", function () { +describe("aggregateConsolidation", () => { const sk0 = SecretKey.fromBytes(Buffer.alloc(32, 1)); const sk1 = SecretKey.fromBytes(Buffer.alloc(32, 2)); const sk2 = SecretKey.fromBytes(Buffer.alloc(32, 3)); diff --git a/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts b/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts index 68efd0751585..98453efaa3b6 100644 --- a/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/attestationPool.test.ts @@ -8,12 +8,11 @@ import {AttestationPool} from "../../../../src/chain/opPools/attestationPool.js" import {getMockedClock} from "../../../mocks/clock.js"; /** Valid signature of random data to prevent BLS errors */ -export const validSignature = fromHexString( +const validSignature = fromHexString( "0xb2afb700f6c561ce5e1b4fedaec9d7c06b822d38c720cf588adfda748860a940adf51634b6788f298c552de40183b5a203b2bbe8b7dd147f0bb5bc97080a12efbb631c8888cb31a99cc4706eb3711865b8ea818c10126e4d818b542e9dbf9ae8" ); -describe("AttestationPool", function () { - /* eslint-disable @typescript-eslint/naming-convention */ +describe("AttestationPool", () => { const config = createChainForkConfig({ ...defaultChainConfig, ELECTRA_FORK_EPOCH: 5, diff --git a/packages/beacon-node/test/unit/chain/opPools/syncCommittee.test.ts b/packages/beacon-node/test/unit/chain/opPools/syncCommittee.test.ts index 507c78d560b6..e91eaa58aa73 100644 --- a/packages/beacon-node/test/unit/chain/opPools/syncCommittee.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/syncCommittee.test.ts @@ -7,7 +7,7 @@ import {Clock} from "../../../../src/util/clock.js"; vi.mock("../../../../src/util/clock.js"); -describe("chain / opPools / SyncCommitteeMessagePool", function () { +describe("chain / opPools / SyncCommitteeMessagePool", () => { let cache: SyncCommitteeMessagePool; const subcommitteeIndex = 2; const indexInSubcommittee = 3; @@ -33,7 +33,7 @@ describe("chain / opPools / SyncCommitteeMessagePool", function () { cache.add(subcommitteeIndex, syncCommittee, indexInSubcommittee); }); - afterEach(function () { + afterEach(() => { vi.clearAllTimers(); vi.clearAllMocks(); }); diff --git a/packages/beacon-node/test/unit/chain/opPools/syncCommitteeContribution.test.ts b/packages/beacon-node/test/unit/chain/opPools/syncCommitteeContribution.test.ts index e34a5d006272..e1bd60a2305e 100644 --- a/packages/beacon-node/test/unit/chain/opPools/syncCommitteeContribution.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/syncCommitteeContribution.test.ts @@ -15,7 +15,7 @@ import {EMPTY_SIGNATURE} from "../../../../src/constants/index.js"; import {renderBitArray} from "../../../utils/render.js"; import {VALID_BLS_SIGNATURE_RAND} from "../../../utils/typeGenerator.js"; -describe("chain / opPools / SyncContributionAndProofPool", function () { +describe("chain / opPools / SyncContributionAndProofPool", () => { let cache: SyncContributionAndProofPool; const beaconBlockRoot = Buffer.alloc(32, 1); const slot = 10; @@ -44,7 +44,7 @@ describe("chain / opPools / SyncContributionAndProofPool", function () { }); }); -describe("replaceIfBetter", function () { +describe("replaceIfBetter", () => { const numParticipants = 2; let bestContribution: SyncContributionFast; // const subnetSize = Math.floor(SYNC_COMMITTEE_SIZE / SYNC_COMMITTEE_SUBNET_COUNT); @@ -77,7 +77,7 @@ describe("replaceIfBetter", function () { }); }); -describe("aggregate", function () { +describe("aggregate", () => { const sks: SecretKey[] = []; let bestContributionBySubnet: Map; beforeAll(async () => { diff --git a/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts b/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts index 9ce121e976d0..c7d0a7801fec 100644 --- a/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts +++ b/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts @@ -98,7 +98,7 @@ describe("PrepareNextSlot scheduler", () => { scheduler.prepareForNextSlot(2 * SLOTS_PER_EPOCH - 1), vi.advanceTimersByTimeAsync((config.SECONDS_PER_SLOT * 1000 * 2) / 3), ]); - expect(chainStub.recomputeForkChoiceHead).toHaveBeenCalledWith(); + expect(chainStub.recomputeForkChoiceHead).toHaveBeenCalledOnce(); expect(regenStub.getBlockSlotState).not.toHaveBeenCalled(); }); diff --git a/packages/beacon-node/test/unit/chain/reprocess.test.ts b/packages/beacon-node/test/unit/chain/reprocess.test.ts index a8160544f509..927e4cf8d05f 100644 --- a/packages/beacon-node/test/unit/chain/reprocess.test.ts +++ b/packages/beacon-node/test/unit/chain/reprocess.test.ts @@ -1,7 +1,7 @@ import {describe, it, expect, beforeEach} from "vitest"; import {ReprocessController} from "../../../src/chain/reprocess.js"; -describe("ReprocessController", function () { +describe("ReprocessController", () => { let controller: ReprocessController; beforeEach(() => { diff --git a/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts index f0d85ce3220f..d417d83e2da9 100644 --- a/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts +++ b/packages/beacon-node/test/unit/chain/rewards/blockRewards.test.ts @@ -10,9 +10,7 @@ import { import { generatePerfTestCachedStateAltair, cachedStateAltairPopulateCaches, - // eslint-disable-next-line import/no-relative-packages } from "../../../../../state-transition/test/perf/util.js"; -// eslint-disable-next-line import/no-relative-packages import {BlockAltairOpts, getBlockAltair} from "../../../../../state-transition/test/perf/block/util.js"; import {computeBlockRewards} from "../../../../src/chain/rewards/blockRewards.js"; diff --git a/packages/beacon-node/test/unit/chain/seenCache/aggregateAndProof.test.ts b/packages/beacon-node/test/unit/chain/seenCache/aggregateAndProof.test.ts index 3118fdcadc43..d83433a649ca 100644 --- a/packages/beacon-node/test/unit/chain/seenCache/aggregateAndProof.test.ts +++ b/packages/beacon-node/test/unit/chain/seenCache/aggregateAndProof.test.ts @@ -6,7 +6,7 @@ import { SeenAggregatedAttestations, } from "../../../../src/chain/seenCache/seenAggregateAndProof.js"; -describe("SeenAggregatedAttestations.isKnown", function () { +describe("SeenAggregatedAttestations.isKnown", () => { const testCases: { id: string; seenAttestingBits: number[]; @@ -62,7 +62,7 @@ describe("SeenAggregatedAttestations.isKnown", function () { } }); -describe("insertDesc", function () { +describe("insertDesc", () => { const testCases: { id: string; arr: number[][]; 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 2a3275536f22..27b5eadecf11 100644 --- a/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts +++ b/packages/beacon-node/test/unit/chain/seenCache/seenGossipBlockInput.test.ts @@ -5,7 +5,6 @@ import {ssz} from "@lodestar/types"; import {SeenGossipBlockInput} from "../../../../src/chain/seenCache/seenGossipBlockInput.js"; import {BlockInputType, GossipedInputType, BlockInput} from "../../../../src/chain/blocks/types.js"; -/* eslint-disable @typescript-eslint/naming-convention */ describe("SeenGossipBlockInput", () => { const chainConfig = createChainForkConfig({ ...defaultChainConfig, diff --git a/packages/beacon-node/test/unit/chain/seenCache/syncCommittee.test.ts b/packages/beacon-node/test/unit/chain/seenCache/syncCommittee.test.ts index 901c19cad9f8..59c67f9cedc2 100644 --- a/packages/beacon-node/test/unit/chain/seenCache/syncCommittee.test.ts +++ b/packages/beacon-node/test/unit/chain/seenCache/syncCommittee.test.ts @@ -5,7 +5,7 @@ import {SeenSyncCommitteeMessages, SeenContributionAndProof} from "../../../../s const NUM_SLOTS_IN_CACHE = 3; -describe("chain / seenCache / SeenSyncCommittee caches", function () { +describe("chain / seenCache / SeenSyncCommittee caches", () => { describe("SeenSyncCommitteeMessages", () => { const slot = 10; const subnet = 2; diff --git a/packages/beacon-node/test/unit/chain/shufflingCache.test.ts b/packages/beacon-node/test/unit/chain/shufflingCache.test.ts index 6295a993c072..d417e555872c 100644 --- a/packages/beacon-node/test/unit/chain/shufflingCache.test.ts +++ b/packages/beacon-node/test/unit/chain/shufflingCache.test.ts @@ -1,50 +1,51 @@ import {describe, it, expect, beforeEach} from "vitest"; - -import {getShufflingDecisionBlock} from "@lodestar/state-transition"; -// eslint-disable-next-line import/no-relative-packages import {generateTestCachedBeaconStateOnlyValidators} from "../../../../state-transition/test/perf/util.js"; import {ShufflingCache} from "../../../src/chain/shufflingCache.js"; -describe("ShufflingCache", function () { +describe("ShufflingCache", () => { const vc = 64; const stateSlot = 100; const state = generateTestCachedBeaconStateOnlyValidators({vc, slot: stateSlot}); - const currentEpoch = state.epochCtx.currentShuffling.epoch; + const currentEpoch = state.epochCtx.epoch; + const currentDecisionRoot = state.epochCtx.currentDecisionRoot; let shufflingCache: ShufflingCache; beforeEach(() => { - shufflingCache = new ShufflingCache(null, {maxShufflingCacheEpochs: 1}); - shufflingCache.processState(state, currentEpoch); + shufflingCache = new ShufflingCache(null, null, {maxShufflingCacheEpochs: 1}, [ + { + shuffling: state.epochCtx.currentShuffling, + decisionRoot: currentDecisionRoot, + }, + ]); }); - it("should get shuffling from cache", async function () { - const decisionRoot = getShufflingDecisionBlock(state, currentEpoch); - expect(await shufflingCache.get(currentEpoch, decisionRoot)).toEqual(state.epochCtx.currentShuffling); + it("should get shuffling from cache", async () => { + expect(await shufflingCache.get(currentEpoch, currentDecisionRoot)).toEqual(state.epochCtx.currentShuffling); }); - it("should bound by maxSize(=1)", async function () { - const decisionRoot = getShufflingDecisionBlock(state, currentEpoch); - expect(await shufflingCache.get(currentEpoch, decisionRoot)).toEqual(state.epochCtx.currentShuffling); + it("should bound by maxSize(=1)", async () => { + expect(await shufflingCache.get(currentEpoch, currentDecisionRoot)).toEqual(state.epochCtx.currentShuffling); // insert promises at the same epoch does not prune the cache shufflingCache.insertPromise(currentEpoch, "0x00"); - expect(await shufflingCache.get(currentEpoch, decisionRoot)).toEqual(state.epochCtx.currentShuffling); - // insert shufflings at other epochs does prune the cache - shufflingCache.processState(state, currentEpoch + 1); + expect(await shufflingCache.get(currentEpoch, currentDecisionRoot)).toEqual(state.epochCtx.currentShuffling); + // insert shuffling at other epochs does prune the cache + shufflingCache["set"](state.epochCtx.previousShuffling, state.epochCtx.previousDecisionRoot); // the current shuffling is not available anymore - expect(await shufflingCache.get(currentEpoch, decisionRoot)).toBeNull(); + expect(await shufflingCache.get(currentEpoch, currentDecisionRoot)).toBeNull(); }); - it("should return shuffling from promise", async function () { - const nextDecisionRoot = getShufflingDecisionBlock(state, currentEpoch + 1); - shufflingCache.insertPromise(currentEpoch + 1, nextDecisionRoot); - const shufflingRequest0 = shufflingCache.get(currentEpoch + 1, nextDecisionRoot); - const shufflingRequest1 = shufflingCache.get(currentEpoch + 1, nextDecisionRoot); - shufflingCache.processState(state, currentEpoch + 1); - expect(await shufflingRequest0).toEqual(state.epochCtx.nextShuffling); - expect(await shufflingRequest1).toEqual(state.epochCtx.nextShuffling); + it("should return shuffling from promise", async () => { + const previousEpoch = state.epochCtx.epoch - 1; + const previousDecisionRoot = state.epochCtx.previousDecisionRoot; + shufflingCache.insertPromise(previousEpoch, previousDecisionRoot); + const shufflingRequest0 = shufflingCache.get(previousEpoch, previousDecisionRoot); + const shufflingRequest1 = shufflingCache.get(previousEpoch, previousDecisionRoot); + shufflingCache["set"](state.epochCtx.previousShuffling, previousDecisionRoot); + expect(await shufflingRequest0).toEqual(state.epochCtx.previousShuffling); + expect(await shufflingRequest1).toEqual(state.epochCtx.previousShuffling); }); - it("should support up to 2 promises at a time", async function () { + it("should support up to 2 promises at a time", async () => { // insert 2 promises at the same epoch shufflingCache.insertPromise(currentEpoch, "0x00"); shufflingCache.insertPromise(currentEpoch, "0x01"); diff --git a/packages/beacon-node/test/unit/chain/stateCache/blockStateCacheImpl.test.ts b/packages/beacon-node/test/unit/chain/stateCache/blockStateCacheImpl.test.ts index b89a71399237..19dc0f3a2c60 100644 --- a/packages/beacon-node/test/unit/chain/stateCache/blockStateCacheImpl.test.ts +++ b/packages/beacon-node/test/unit/chain/stateCache/blockStateCacheImpl.test.ts @@ -7,7 +7,7 @@ import {BlockStateCacheImpl} from "../../../../src/chain/stateCache/index.js"; import {generateCachedState} from "../../../utils/state.js"; import {ZERO_HASH} from "../../../../src/constants/index.js"; -describe("BlockStateCacheImpl", function () { +describe("BlockStateCacheImpl", () => { let cache: BlockStateCacheImpl; let key1: Root, key2: Root; const shuffling: EpochShuffling = { @@ -18,7 +18,7 @@ describe("BlockStateCacheImpl", function () { committeesPerSlot: 1, }; - beforeEach(function () { + beforeEach(() => { // max 2 items cache = new BlockStateCacheImpl({maxStates: 2}); const state1 = generateCachedState({slot: 0}); @@ -31,7 +31,7 @@ describe("BlockStateCacheImpl", function () { cache.add(state2); }); - it("should prune", function () { + it("should prune", () => { expect(cache.size).toBe(2); const state3 = generateCachedState({slot: 2 * SLOTS_PER_EPOCH}); state3.epochCtx.currentShuffling = {...shuffling, epoch: 2}; @@ -46,7 +46,7 @@ describe("BlockStateCacheImpl", function () { expect(cache.get(toHexString(key2))).toBeDefined(); }); - it("should deleteAllBeforeEpoch", function () { + it("should deleteAllBeforeEpoch", () => { cache.deleteAllBeforeEpoch(2); expect(cache.size).toBe(0); }); diff --git a/packages/beacon-node/test/unit/chain/stateCache/fifoBlockStateCache.test.ts b/packages/beacon-node/test/unit/chain/stateCache/fifoBlockStateCache.test.ts index b4aac92dd9bb..07a8ec12093d 100644 --- a/packages/beacon-node/test/unit/chain/stateCache/fifoBlockStateCache.test.ts +++ b/packages/beacon-node/test/unit/chain/stateCache/fifoBlockStateCache.test.ts @@ -5,7 +5,7 @@ import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {FIFOBlockStateCache} from "../../../../src/chain/stateCache/index.js"; import {generateCachedState} from "../../../utils/state.js"; -describe("FIFOBlockStateCache", function () { +describe("FIFOBlockStateCache", () => { let cache: FIFOBlockStateCache; const shuffling: EpochShuffling = { epoch: 0, @@ -27,7 +27,7 @@ describe("FIFOBlockStateCache", function () { const key3 = toHexString(state3.hashTreeRoot()); state3.epochCtx.currentShuffling = {...shuffling, epoch: 2}; - beforeEach(function () { + beforeEach(() => { // max 2 items cache = new FIFOBlockStateCache({maxBlockStates: 2}, {}); cache.add(state1); diff --git a/packages/beacon-node/test/unit/chain/stateCache/inMemoryCheckpointsCache.test.ts b/packages/beacon-node/test/unit/chain/stateCache/inMemoryCheckpointsCache.test.ts index 59f320178118..23a792bef0a8 100644 --- a/packages/beacon-node/test/unit/chain/stateCache/inMemoryCheckpointsCache.test.ts +++ b/packages/beacon-node/test/unit/chain/stateCache/inMemoryCheckpointsCache.test.ts @@ -9,7 +9,7 @@ import { } from "../../../../src/chain/stateCache/inMemoryCheckpointsCache.js"; import {generateCachedState} from "../../../utils/state.js"; -describe("InMemoryCheckpointStateCache", function () { +describe("InMemoryCheckpointStateCache", () => { let root0a: Buffer, root0b: Buffer, root1: Buffer, root2: Buffer; let cp0a: phase0.Checkpoint, cp0b: phase0.Checkpoint, cp1: phase0.Checkpoint, cp2: phase0.Checkpoint; let cp0aHex: CheckpointHex, cp0bHex: CheckpointHex, cp1Hex: CheckpointHex, cp2Hex: CheckpointHex; diff --git a/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts b/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts index 9614263b4312..f98b180fa983 100644 --- a/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts +++ b/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts @@ -12,7 +12,7 @@ import {getTestDatastore} from "../../../utils/chain/stateCache/datastore.js"; import {CheckpointHex} from "../../../../src/chain/stateCache/types.js"; import {FIFOBlockStateCache, toCheckpointHex} from "../../../../src/chain/index.js"; -describe("PersistentCheckpointStateCache", function () { +describe("PersistentCheckpointStateCache", () => { let root0a: Buffer, root0b: Buffer, root1: Buffer, root2: Buffer; let cp0a: phase0.Checkpoint, cp0b: phase0.Checkpoint, cp1: phase0.Checkpoint, cp2: phase0.Checkpoint; let cp0aHex: CheckpointHex, cp0bHex: CheckpointHex, cp1Hex: CheckpointHex, cp2Hex: CheckpointHex; @@ -135,7 +135,7 @@ describe("PersistentCheckpointStateCache", function () { expect((await cache.getOrReloadLatest(cp0bHex.rootHex, cp0b.epoch - 1))?.serialize()).toBeUndefined(); }); - it("pruneFinalized and getStateOrBytes", async function () { + it("pruneFinalized and getStateOrBytes", async () => { cache.add(cp2, states["cp2"]); expect(((await cache.getStateOrBytes(cp0bHex)) as CachedBeaconStateAllForks).hashTreeRoot()).toEqual( states["cp0b"].hashTreeRoot() @@ -182,7 +182,7 @@ describe("PersistentCheckpointStateCache", function () { // |0b--------root1--------root2 // | // 0a - it("single state at lowest memory epoch", async function () { + it("single state at lowest memory epoch", async () => { cache.add(cp2, states["cp2"]); expect(await cache.processState(toHexString(cp2.root), states["cp2"])).toEqual(1); expect(cache.findSeedStateToReload(cp0aHex)?.hashTreeRoot()).toEqual(states["cp1"].hashTreeRoot()); @@ -198,7 +198,7 @@ describe("PersistentCheckpointStateCache", function () { // 0a------------------------------root3 // ^ ^ // cp1a={0a, 21} {0a, 22}=cp2a - it("multiple states at lowest memory epoch", async function () { + it("multiple states at lowest memory epoch", async () => { cache.add(cp2, states["cp2"]); expect(await cache.processState(toHexString(cp2.root), states["cp2"])).toEqual(1); @@ -259,7 +259,7 @@ describe("PersistentCheckpointStateCache", function () { // |0b--------root1--------root2-----root3 // | // 0a - it("no reorg", async function () { + it("no reorg", async () => { expect(fileApisBuffer.size).toEqual(0); cache.add(cp2, states["cp2"]); expect(await cache.processState(toHexString(cp2.root), states["cp2"])).toEqual(1); @@ -293,7 +293,7 @@ describe("PersistentCheckpointStateCache", function () { // |0b--------root1--------root2-root3 | // | | // 0a |---------root4 - it("reorg in same epoch", async function () { + it("reorg in same epoch", async () => { // mostly the same to the above test expect(fileApisBuffer.size).toEqual(0); cache.add(cp2, states["cp2"]); @@ -338,7 +338,7 @@ describe("PersistentCheckpointStateCache", function () { // 1a ^ // | // {1a, 22}=cp2a - it("reorg 1 epoch", async function () { + it("reorg 1 epoch", async () => { // process root2 state cache.add(cp2, states["cp2"]); expect(await cache.processState(toHexString(cp2.root), states["cp2"])).toEqual(1); @@ -382,7 +382,7 @@ describe("PersistentCheckpointStateCache", function () { // 0a ^ ^ // | | // cp1a={0a, 21} {0a, 22}=cp2a - it("reorg 2 epochs", async function () { + it("reorg 2 epochs", async () => { // process root2 state cache.add(cp2, states["cp2"]); expect(await cache.processState(toHexString(cp2.root), states["cp2"])).toEqual(1); @@ -435,7 +435,7 @@ describe("PersistentCheckpointStateCache", function () { // ^ ^ // | | // cp1a={0a, 21} {0a, 22}=cp2a - it("reorg 3 epochs, persist cp 0a", async function () { + it("reorg 3 epochs, persist cp 0a", async () => { // process root2 state cache.add(cp2, states["cp2"]); expect(await cache.processState(toHexString(cp2.root), states["cp2"])).toEqual(1); @@ -491,7 +491,7 @@ describe("PersistentCheckpointStateCache", function () { // 0a ^ ^ // | | // cp1b={0b, 21} {0b, 22}=cp2b - it("reorg 3 epochs, prune but no persist", async function () { + it("reorg 3 epochs, prune but no persist", async () => { // process root2 state cache.add(cp2, states["cp2"]); expect(await cache.processState(toHexString(cp2.root), states["cp2"])).toEqual(1); diff --git a/packages/beacon-node/test/unit/chain/validation/aggregateAndProof.test.ts b/packages/beacon-node/test/unit/chain/validation/aggregateAndProof.test.ts index f2c20fd5cbe4..e2bc392b4ea2 100644 --- a/packages/beacon-node/test/unit/chain/validation/aggregateAndProof.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/aggregateAndProof.test.ts @@ -2,7 +2,6 @@ import {BitArray, toHexString} from "@chainsafe/ssz"; import {describe, it} from "vitest"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {phase0, ssz} from "@lodestar/types"; -// eslint-disable-next-line import/no-relative-packages import {generateTestCachedBeaconStateOnlyValidators} from "../../../../../state-transition/test/perf/util.js"; import {IBeaconChain} from "../../../../src/chain/index.js"; import {AttestationErrorCode} from "../../../../src/chain/errors/index.js"; @@ -24,7 +23,6 @@ describe("chain / validation / aggregateAndProof", () => { const getState = memoOnce(() => generateTestCachedBeaconStateOnlyValidators({vc, slot: stateSlot})); - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type function getValidData(opts?: Partial) { return getAggregateAndProofValidData({ currentSlot: stateSlot, diff --git a/packages/beacon-node/test/unit/chain/validation/attestation/getShufflingForAttestationVerification.test.ts b/packages/beacon-node/test/unit/chain/validation/attestation/getShufflingForAttestationVerification.test.ts index a0eb147db8e8..4ba65270e17a 100644 --- a/packages/beacon-node/test/unit/chain/validation/attestation/getShufflingForAttestationVerification.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/attestation/getShufflingForAttestationVerification.test.ts @@ -1,6 +1,5 @@ import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"; // We need to import the mock before the packages -// eslint-disable-next-line import/order import {MockedBeaconChain, getMockedBeaconChain} from "../../../../mocks/mockedBeaconChain.js"; import {EpochShuffling, computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {EpochDifference, ProtoBlock} from "@lodestar/fork-choice"; @@ -41,17 +40,15 @@ describe("getShufflingForAttestationVerification", () => { forkchoiceStub.getDependentRoot.mockImplementationOnce((block, epochDiff) => { if (block === attHeadBlock && epochDiff === EpochDifference.previous) { return previousDependentRoot; - } else { - throw new Error("Unexpected input"); } + throw new Error("Unexpected input"); }); const expectedShuffling = {epoch: attEpoch} as EpochShuffling; shufflingCacheStub.get.mockImplementationOnce((epoch, root) => { if (epoch === attEpoch && root === previousDependentRoot) { return Promise.resolve(expectedShuffling); - } else { - return Promise.resolve(null); } + return Promise.resolve(null); }); const resultShuffling = await getShufflingForAttestationVerification( chain, @@ -73,17 +70,15 @@ describe("getShufflingForAttestationVerification", () => { forkchoiceStub.getDependentRoot.mockImplementationOnce((block, epochDiff) => { if (block === attHeadBlock && epochDiff === EpochDifference.current) { return currentDependentRoot; - } else { - throw new Error("Unexpected input"); } + throw new Error("Unexpected input"); }); const expectedShuffling = {epoch: attEpoch} as EpochShuffling; shufflingCacheStub.get.mockImplementationOnce((epoch, root) => { if (epoch === attEpoch && root === currentDependentRoot) { return Promise.resolve(expectedShuffling); - } else { - return Promise.resolve(null); } + return Promise.resolve(null); }); const resultShuffling = await getShufflingForAttestationVerification( chain, @@ -108,12 +103,10 @@ describe("getShufflingForAttestationVerification", () => { if (callCount === 0) { callCount++; return Promise.resolve(null); - } else { - return Promise.resolve(expectedShuffling); } - } else { - return Promise.resolve(null); + return Promise.resolve(expectedShuffling); } + return Promise.resolve(null); }); chain.regenStateForAttestationVerification.mockImplementationOnce(() => Promise.resolve(expectedShuffling)); diff --git a/packages/beacon-node/test/unit/chain/validation/attestation/validateAttestation.test.ts b/packages/beacon-node/test/unit/chain/validation/attestation/validateAttestation.test.ts index 45d293ffbb33..be9fd3587b7c 100644 --- a/packages/beacon-node/test/unit/chain/validation/attestation/validateAttestation.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/attestation/validateAttestation.test.ts @@ -2,7 +2,7 @@ import {BitArray} from "@chainsafe/ssz"; import {describe, expect, it} from "vitest"; import {ForkName, SLOTS_PER_EPOCH} from "@lodestar/params"; import {ssz} from "@lodestar/types"; -// eslint-disable-next-line import/no-relative-packages +import {LodestarError} from "@lodestar/utils"; import {generateTestCachedBeaconStateOnlyValidators} from "../../../../../../state-transition/test/perf/util.js"; import {AttestationErrorCode, GossipErrorCode} from "../../../../../src/chain/errors/index.js"; import {IBeaconChain} from "../../../../../src/chain/index.js"; @@ -12,7 +12,7 @@ import { getSeenAttDataKeyFromGossipAttestation, getSeenAttDataKeyFromSignedAggregateAndProof, validateApiAttestation, - validateAttestation, + validateGossipAttestationsSameAttData, } from "../../../../../src/chain/validation/index.js"; import {getAttDataFromAttestationSerialized} from "../../../../../src/util/sszBytes.js"; import {memoOnce} from "../../../../utils/cache.js"; @@ -30,7 +30,6 @@ describe("validateAttestation", () => { const getState = memoOnce(() => generateTestCachedBeaconStateOnlyValidators({vc, slot: stateSlot})); - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type function getValidData(opts?: Partial) { return getAttestationValidData({ currentSlot: stateSlot, @@ -75,7 +74,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.BAD_TARGET_EPOCH @@ -94,7 +93,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.PAST_SLOT @@ -113,7 +112,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.FUTURE_SLOT @@ -138,7 +137,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.NOT_EXACTLY_ONE_AGGREGATION_BIT_SET @@ -158,7 +157,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.NOT_EXACTLY_ONE_AGGREGATION_BIT_SET @@ -182,7 +181,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.UNKNOWN_OR_PREFINALIZED_BEACON_BLOCK_ROOT @@ -202,7 +201,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.INVALID_TARGET_ROOT @@ -229,7 +228,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.WRONG_NUMBER_OF_AGGREGATION_BITS @@ -248,7 +247,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, invalidSubnet, AttestationErrorCode.INVALID_SUBNET_ID @@ -268,7 +267,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.ATTESTATION_ALREADY_KNOWN @@ -290,7 +289,7 @@ describe("validateAttestation", () => { attestation: null, serializedData, attSlot: attestation.data.slot, - attDataBase64: getAttDataFromAttestationSerialized(serializedData), + attDataBase64: getAttDataFromAttestationSerialized(serializedData) as string, }, subnet, AttestationErrorCode.INVALID_SIGNATURE @@ -314,7 +313,9 @@ describe("validateAttestation", () => { errorCode: string ): Promise { const fork = chain.config.getForkName(stateSlot); - await expectRejectedWithLodestarError(validateAttestation(fork, chain, attestationOrBytes, subnet), errorCode); + const {results} = await validateGossipAttestationsSameAttData(fork, chain, [attestationOrBytes], subnet); + expect(results.length).toEqual(1); + expect((results[0].err as LodestarError<{code: string}>).type.code).toEqual(errorCode); } }); 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 d90ca4a54d24..f8a2b7245ef7 100644 --- a/packages/beacon-node/test/unit/chain/validation/block.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/block.test.ts @@ -12,7 +12,7 @@ import {EMPTY_SIGNATURE, ZERO_HASH} from "../../../../src/constants/index.js"; import {expectRejectedWithLodestarError} from "../../../utils/errors.js"; import {generateCachedState} from "../../../utils/state.js"; -describe("gossip block validation", function () { +describe("gossip block validation", () => { let chain: MockedBeaconChain; let forkChoice: MockedBeaconChain["forkChoice"]; let regen: Mocked; @@ -25,7 +25,7 @@ describe("gossip block validation", function () { const signature = EMPTY_SIGNATURE; const maxSkipSlots = 10; - beforeEach(function () { + beforeEach(() => { chain = getMockedBeaconChain(); vi.spyOn(chain.clock, "currentSlotWithGossipDisparity", "get").mockReturnValue(clockSlot); forkChoice = chain.forkChoice; @@ -33,7 +33,6 @@ describe("gossip block validation", function () { chain.forkChoice = forkChoice; regen = chain.regen; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (chain as any).opts = {maxSkipSlots}; verifySignature = chain.bls.verifySignatureSets; @@ -50,7 +49,7 @@ describe("gossip block validation", function () { job = {signature, message: block}; }); - it("FUTURE_SLOT", async function () { + it("FUTURE_SLOT", async () => { // Set the block slot to after the current clock const signedBlock = {signature, message: {...block, slot: clockSlot + 1}}; @@ -60,7 +59,7 @@ describe("gossip block validation", function () { ); }); - it("WOULD_REVERT_FINALIZED_SLOT", async function () { + it("WOULD_REVERT_FINALIZED_SLOT", async () => { // Set finalized epoch to be greater than block's epoch forkChoice.getFinalizedCheckpoint.mockReturnValue({epoch: Infinity, root: ZERO_HASH, rootHex: ""}); @@ -70,7 +69,7 @@ describe("gossip block validation", function () { ); }); - it("ALREADY_KNOWN", async function () { + it("ALREADY_KNOWN", async () => { // Make the fork choice return a block summary for the proposed block forkChoice.getBlockHex.mockReturnValue({} as ProtoBlock); @@ -80,7 +79,7 @@ describe("gossip block validation", function () { ); }); - it("REPEAT_PROPOSAL", async function () { + it("REPEAT_PROPOSAL", async () => { // Register the proposer as known chain.seenBlockProposers.add(job.message.slot, job.message.proposerIndex); @@ -90,7 +89,7 @@ describe("gossip block validation", function () { ); }); - it("PARENT_UNKNOWN (fork-choice)", async function () { + it("PARENT_UNKNOWN (fork-choice)", async () => { // Return not known for proposed block forkChoice.getBlockHex.mockReturnValueOnce(null); // Return not known for parent block too @@ -102,7 +101,7 @@ describe("gossip block validation", function () { ); }); - it("TOO_MANY_SKIPPED_SLOTS", async function () { + it("TOO_MANY_SKIPPED_SLOTS", async () => { // Return not known for proposed block forkChoice.getBlockHex.mockReturnValueOnce(null); // Return parent block with 1 slot way back than maxSkipSlots @@ -114,7 +113,7 @@ describe("gossip block validation", function () { ); }); - it("NOT_LATER_THAN_PARENT", async function () { + it("NOT_LATER_THAN_PARENT", async () => { // Return not known for proposed block forkChoice.getBlockHex.mockReturnValueOnce(null); // Returned parent block is latter than proposed block @@ -126,7 +125,7 @@ describe("gossip block validation", function () { ); }); - it("PARENT_UNKNOWN (regen)", async function () { + it("PARENT_UNKNOWN (regen)", async () => { // Return not known for proposed block forkChoice.getBlockHex.mockReturnValueOnce(null); // Returned parent block is latter than proposed block @@ -140,7 +139,7 @@ describe("gossip block validation", function () { ); }); - it("PROPOSAL_SIGNATURE_INVALID", async function () { + it("PROPOSAL_SIGNATURE_INVALID", async () => { // Return not known for proposed block forkChoice.getBlockHex.mockReturnValueOnce(null); // Returned parent block is latter than proposed block @@ -156,7 +155,7 @@ describe("gossip block validation", function () { ); }); - it("INCORRECT_PROPOSER", async function () { + it("INCORRECT_PROPOSER", async () => { // Return not known for proposed block forkChoice.getBlockHex.mockReturnValueOnce(null); // Returned parent block is latter than proposed block @@ -175,7 +174,7 @@ describe("gossip block validation", function () { ); }); - it("valid", async function () { + it("valid", async () => { // Return not known for proposed block forkChoice.getBlockHex.mockReturnValueOnce(null); // Returned parent block is latter than proposed block diff --git a/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts b/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts index ef4364abc366..cbd231c926ce 100644 --- a/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts @@ -8,11 +8,10 @@ import {LightClientErrorCode} from "../../../../src/chain/errors/lightClientErro import {IBeaconChain} from "../../../../src/chain/index.js"; import {getMockedBeaconChain} from "../../../mocks/mockedBeaconChain.js"; -describe("Light Client Finality Update validation", function () { +describe("Light Client Finality Update validation", () => { const afterEachCallbacks: (() => Promise | void)[] = []; const config = createChainForkConfig({ ...defaultChainConfig, - /* eslint-disable @typescript-eslint/naming-convention */ ALTAIR_FORK_EPOCH: 1, BELLATRIX_FORK_EPOCH: 3, CAPELLA_FORK_EPOCH: Infinity, diff --git a/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts b/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts index b63a08757380..7665b4f89179 100644 --- a/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts @@ -8,11 +8,10 @@ import {LightClientErrorCode} from "../../../../src/chain/errors/lightClientErro import {IBeaconChain} from "../../../../src/chain/index.js"; import {getMockedBeaconChain} from "../../../mocks/mockedBeaconChain.js"; -describe("Light Client Optimistic Update validation", function () { +describe("Light Client Optimistic Update validation", () => { const afterEachCallbacks: (() => Promise | void)[] = []; const config = createChainForkConfig({ ...defaultChainConfig, - /* eslint-disable @typescript-eslint/naming-convention */ ALTAIR_FORK_EPOCH: 1, BELLATRIX_FORK_EPOCH: 3, CAPELLA_FORK_EPOCH: Infinity, diff --git a/packages/beacon-node/test/unit/chain/validation/syncCommittee.test.ts b/packages/beacon-node/test/unit/chain/validation/syncCommittee.test.ts index b498333ae738..cbffc2a4ffd9 100644 --- a/packages/beacon-node/test/unit/chain/validation/syncCommittee.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/syncCommittee.test.ts @@ -12,7 +12,7 @@ import {SeenSyncCommitteeMessages} from "../../../../src/chain/seenCache/index.j import {ZERO_HASH} from "../../../../src/constants/constants.js"; // https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/altair/p2p-interface.md -describe("Sync Committee Signature validation", function () { +describe("Sync Committee Signature validation", () => { let chain: MockedBeaconChain; let clockStub: MockedBeaconChain["clock"]; let forkchoiceStub: MockedBeaconChain["forkChoice"]; @@ -20,21 +20,20 @@ describe("Sync Committee Signature validation", function () { let altairForkEpochBk: Epoch; const altairForkEpoch = 2020; const currentSlot = SLOTS_PER_EPOCH * (altairForkEpoch + 1); - // eslint-disable-next-line @typescript-eslint/naming-convention const config = createChainForkConfig(Object.assign({}, defaultChainConfig, {ALTAIR_FORK_EPOCH: altairForkEpoch})); // all validators have same pubkey const validatorIndexInSyncCommittee = 15; - beforeAll(async function () { + beforeAll(async () => { altairForkEpochBk = config.ALTAIR_FORK_EPOCH; config.ALTAIR_FORK_EPOCH = altairForkEpoch; }); - afterAll(function () { + afterAll(() => { config.ALTAIR_FORK_EPOCH = altairForkEpochBk; }); - beforeEach(function () { + beforeEach(() => { chain = getMockedBeaconChain(); ( chain as { @@ -46,11 +45,11 @@ describe("Sync Committee Signature validation", function () { vi.spyOn(clockStub, "isCurrentSlotGivenGossipDisparity").mockReturnValue(true); }); - afterEach(function () { + afterEach(() => { vi.clearAllMocks(); }); - it("should throw error - the signature's slot is in the past", async function () { + it("should throw error - the signature's slot is in the past", async () => { (clockStub.isCurrentSlotGivenGossipDisparity as Mock).mockReturnValue(false); vi.spyOn(clockStub, "currentSlot", "get").mockReturnValue(100); @@ -61,7 +60,7 @@ describe("Sync Committee Signature validation", function () { ); }); - it("should throw error - messageRoot is same to prevRoot", async function () { + it("should throw error - messageRoot is same to prevRoot", async () => { const syncCommittee = getSyncCommitteeSignature(currentSlot, validatorIndexInSyncCommittee); const headState = generateCachedAltairState({slot: currentSlot}, altairForkEpoch); chain.getHeadState.mockReturnValue(headState); @@ -72,7 +71,7 @@ describe("Sync Committee Signature validation", function () { ); }); - it("should throw error - messageRoot is different to prevRoot but not forkchoice head", async function () { + it("should throw error - messageRoot is different to prevRoot but not forkchoice head", async () => { const syncCommittee = getSyncCommitteeSignature(currentSlot, validatorIndexInSyncCommittee); const headState = generateCachedAltairState({slot: currentSlot}, altairForkEpoch); chain.getHeadState.mockReturnValue(headState); @@ -85,7 +84,7 @@ describe("Sync Committee Signature validation", function () { ); }); - it("should throw error - the validator is not part of the current sync committee", async function () { + it("should throw error - the validator is not part of the current sync committee", async () => { const syncCommittee = getSyncCommitteeSignature(currentSlot, 100); const headState = generateCachedAltairState({slot: currentSlot}, altairForkEpoch); chain.getHeadState.mockReturnValue(headState); @@ -100,7 +99,7 @@ describe("Sync Committee Signature validation", function () { * Skip this spec check: [REJECT] The subnet_id is correct, i.e. subnet_id in compute_subnets_for_sync_committee(state, sync_committee_signature.validator_index) * because it's the same to VALIDATOR_NOT_IN_SYNC_COMMITTEE */ - it.skip("should throw error - incorrect subnet", async function () { + it.skip("should throw error - incorrect subnet", async () => { const syncCommittee = getSyncCommitteeSignature(currentSlot, 1); const headState = generateCachedAltairState({slot: currentSlot}, altairForkEpoch); chain.getHeadState.mockReturnValue(headState); @@ -110,7 +109,7 @@ describe("Sync Committee Signature validation", function () { ); }); - it("should throw error - invalid signature", async function () { + it("should throw error - invalid signature", async () => { const syncCommittee = getSyncCommitteeSignature(currentSlot, validatorIndexInSyncCommittee); const headState = generateCachedAltairState({slot: currentSlot}, altairForkEpoch); @@ -122,7 +121,7 @@ describe("Sync Committee Signature validation", function () { ); }); - it("should pass, no prev root", async function () { + it("should pass, no prev root", async () => { const syncCommittee = getSyncCommitteeSignature(currentSlot, validatorIndexInSyncCommittee); const subnet = 3; const {slot, validatorIndex} = syncCommittee; @@ -143,7 +142,7 @@ describe("Sync Committee Signature validation", function () { ); }); - it("should pass, there is prev root but message root is forkchoice head", async function () { + it("should pass, there is prev root but message root is forkchoice head", async () => { const syncCommittee = getSyncCommitteeSignature(currentSlot, validatorIndexInSyncCommittee); const headState = generateCachedAltairState({slot: currentSlot}, altairForkEpoch); diff --git a/packages/beacon-node/test/unit/db/api/repositories/blockArchive.test.ts b/packages/beacon-node/test/unit/db/api/repositories/blockArchive.test.ts index 532016242f95..b87e9b926fdd 100644 --- a/packages/beacon-node/test/unit/db/api/repositories/blockArchive.test.ts +++ b/packages/beacon-node/test/unit/db/api/repositories/blockArchive.test.ts @@ -9,21 +9,21 @@ import {BlockArchiveRepository} from "../../../../../src/db/repositories/index.j import {testLogger} from "../../../../utils/logger.js"; import {Bucket} from "../../../../../src/db/buckets.js"; -describe("block archive repository", function () { +describe("block archive repository", () => { const testDir = "./.tmp"; let blockArchive: BlockArchiveRepository; let db: LevelDbController; - beforeEach(async function () { + beforeEach(async () => { db = await LevelDbController.create({name: testDir}, {logger: testLogger()}); blockArchive = new BlockArchiveRepository(config, db); }); - afterEach(async function () { + afterEach(async () => { await db.close(); rimraf.sync(testDir); }); - it("should retrieve blocks in order", async function () { + it("should retrieve blocks in order", async () => { await blockArchive.batchPut( Array.from({length: 1001}, (_, i) => { const slot = i; @@ -106,7 +106,7 @@ describe("block archive repository", function () { } }); - it("should store indexes when adding single block", async function () { + it("should store indexes when adding single block", async () => { const spy = vi.spyOn(db, "put"); const block = ssz.phase0.SignedBeaconBlock.defaultValue(); await blockArchive.add(block); @@ -120,7 +120,7 @@ describe("block archive repository", function () { ); }); - it("should store indexes when block batch", async function () { + it("should store indexes when block batch", async () => { const spy = vi.spyOn(db, "put"); const blocks = [ssz.phase0.SignedBeaconBlock.defaultValue(), ssz.phase0.SignedBeaconBlock.defaultValue()]; await blockArchive.batchAdd(blocks); @@ -152,14 +152,14 @@ describe("block archive repository", function () { ); }); - it("should get slot by root", async function () { + it("should get slot by root", async () => { const block = ssz.phase0.SignedBeaconBlock.defaultValue(); await blockArchive.add(block); const slot = await blockArchive.getSlotByRoot(ssz.phase0.BeaconBlock.hashTreeRoot(block.message)); expect(slot).toBe(block.message.slot); }); - it("should get block by root", async function () { + it("should get block by root", async () => { const block = ssz.phase0.SignedBeaconBlock.defaultValue(); await blockArchive.add(block); const retrieved = await blockArchive.getByRoot(ssz.phase0.BeaconBlock.hashTreeRoot(block.message)); @@ -167,14 +167,14 @@ describe("block archive repository", function () { expect(ssz.phase0.SignedBeaconBlock.equals(retrieved, block)).toBe(true); }); - it("should get slot by parent root", async function () { + it("should get slot by parent root", async () => { const block = ssz.phase0.SignedBeaconBlock.defaultValue(); await blockArchive.add(block); const slot = await blockArchive.getSlotByParentRoot(block.message.parentRoot); expect(slot).toBe(block.message.slot); }); - it("should get block by parent root", async function () { + it("should get block by parent root", async () => { const block = ssz.phase0.SignedBeaconBlock.defaultValue(); await blockArchive.add(block); const retrieved = await blockArchive.getByParentRoot(block.message.parentRoot); diff --git a/packages/beacon-node/test/unit/db/api/repository.test.ts b/packages/beacon-node/test/unit/db/api/repository.test.ts index 07c199632125..3b2840b3f0be 100644 --- a/packages/beacon-node/test/unit/db/api/repository.test.ts +++ b/packages/beacon-node/test/unit/db/api/repository.test.ts @@ -11,7 +11,6 @@ vi.mock("@lodestar/db", async (importOriginal) => { return { ...mod, - // eslint-disable-next-line @typescript-eslint/naming-convention LevelDbController: vi.spyOn(mod, "LevelDbController").mockImplementation(() => { return { get: vi.fn(), @@ -31,7 +30,6 @@ interface TestType { bytes: Bytes32; } -// eslint-disable-next-line @typescript-eslint/naming-convention const TestSSZType = new ContainerType({ bool: ssz.Boolean, bytes: ssz.Bytes32, @@ -43,10 +41,10 @@ class TestRepository extends Repository { } } -describe("database repository", function () { +describe("database repository", () => { let repository: TestRepository, controller: MockedObject; - beforeEach(function () { + beforeEach(() => { controller = vi.mocked(new LevelDbController({} as any, {} as any, {} as any)); repository = new TestRepository(controller as unknown as LevelDbController); }); @@ -55,7 +53,7 @@ describe("database repository", function () { vi.clearAllMocks(); }); - it("should get single item", async function () { + it("should get single item", async () => { const item = {bool: true, bytes: Buffer.alloc(32)}; controller.get.mockResolvedValue(TestSSZType.serialize(item) as Buffer); const result = await repository.get("id"); @@ -63,14 +61,14 @@ describe("database repository", function () { expect(controller.get).toHaveBeenCalledTimes(1); }); - it("should return null if item not found", async function () { + it("should return null if item not found", async () => { controller.get.mockResolvedValue(null); const result = await repository.get("id"); expect(result).toEqual(null); expect(controller.get).toHaveBeenCalledTimes(1); }); - it("should return true if item exists", async function () { + it("should return true if item exists", async () => { const item = {bool: true, bytes: Buffer.alloc(32)}; controller.get.mockResolvedValue(TestSSZType.serialize(item) as Buffer); const result = await repository.has("id"); @@ -78,31 +76,31 @@ describe("database repository", function () { expect(controller.get).toHaveBeenCalledTimes(1); }); - it("should return false if item doesnt exists", async function () { + it("should return false if item doesnt exists", async () => { controller.get.mockResolvedValue(null); const result = await repository.has("id"); expect(result).toBe(false); expect(controller.get).toHaveBeenCalledTimes(1); }); - it("should store with hashTreeRoot as id", async function () { + it("should store with hashTreeRoot as id", async () => { const item = {bool: true, bytes: Buffer.alloc(32)}; await expect(repository.add(item)).resolves.toBeUndefined(); expect(controller.put).toHaveBeenCalledTimes(1); }); - it("should store with given id", async function () { + it("should store with given id", async () => { const item = {bool: true, bytes: Buffer.alloc(32)}; await expect(repository.put("1", item)).resolves.toBeUndefined(); expect(controller.put).toHaveBeenCalledTimes(1); }); - it("should delete", async function () { + it("should delete", async () => { await expect(repository.delete("1")).resolves.toBeUndefined(); expect(controller.delete).toHaveBeenCalledTimes(1); }); - it("should return all items", async function () { + it("should return all items", async () => { const item = {bool: true, bytes: Buffer.alloc(32)}; const itemSerialized = TestSSZType.serialize(item); const items = [itemSerialized, itemSerialized, itemSerialized]; @@ -112,24 +110,24 @@ describe("database repository", function () { expect(controller.values).toHaveBeenCalledTimes(1); }); - it("should return range of items", async function () { + it("should return range of items", async () => { await repository.values({gt: "a", lt: "b"}); expect(controller.values).toHaveBeenCalledTimes(1); }); - it("should delete given items", async function () { + it("should delete given items", async () => { await repository.batchDelete(["1", "2", "3"]); expect(controller.batchDelete.mock.calls[0][0]).toHaveLength(3); }); - it("should delete given items by value", async function () { + it("should delete given items by value", async () => { const item = {bool: true, bytes: Buffer.alloc(32)}; await repository.batchRemove([item, item]); expect(controller.batchDelete.mock.calls[0][0]).toHaveLength(2); }); - it("should add multiple values", async function () { + it("should add multiple values", async () => { await repository.batchAdd([ {bool: true, bytes: Buffer.alloc(32)}, {bool: false, bytes: Buffer.alloc(32)}, @@ -138,7 +136,7 @@ describe("database repository", function () { expect(controller.batchPut.mock.calls[0][0]).toHaveLength(2); }); - it("should fetch values stream", async function () { + it("should fetch values stream", async () => { async function* sample(): AsyncGenerator { yield TestSSZType.serialize({bool: true, bytes: Buffer.alloc(32)}) as Buffer; yield TestSSZType.serialize({bool: false, bytes: Buffer.alloc(32)}) as Buffer; diff --git a/packages/beacon-node/test/unit/db/buckets.test.ts b/packages/beacon-node/test/unit/db/buckets.test.ts index 0fb09af95cbc..c7c11d831427 100644 --- a/packages/beacon-node/test/unit/db/buckets.test.ts +++ b/packages/beacon-node/test/unit/db/buckets.test.ts @@ -6,7 +6,7 @@ describe("db buckets", () => { let prevBucket = -1; for (const key of Object.keys(Bucket)) { - if (isNaN(parseInt(key))) { + if (Number.isNaN(parseInt(key))) { const bucket = (Bucket as unknown as Record)[key]; if (bucket < prevBucket) { throw Error(`Bucket ${key} not sorted`); diff --git a/packages/beacon-node/test/unit/eth1/eth1DepositDataTracker.test.ts b/packages/beacon-node/test/unit/eth1/eth1DepositDataTracker.test.ts index d0bd3faffcf8..40988ed21728 100644 --- a/packages/beacon-node/test/unit/eth1/eth1DepositDataTracker.test.ts +++ b/packages/beacon-node/test/unit/eth1/eth1DepositDataTracker.test.ts @@ -8,7 +8,7 @@ import {defaultEth1Options} from "../../../src/eth1/options.js"; import {BeaconDb} from "../../../src/db/beacon.js"; import {getMockedBeaconDb} from "../../mocks/mockedBeaconDb.js"; -describe("Eth1DepositDataTracker", function () { +describe("Eth1DepositDataTracker", () => { const controller = new AbortController(); const logger = testLogger(); @@ -43,7 +43,7 @@ describe("Eth1DepositDataTracker", function () { vi.clearAllMocks(); }); - it("Should dynamically adjust blocks batch size", async function () { + it("Should dynamically adjust blocks batch size", async () => { let expectedSize = 1000; expect(eth1DepositDataTracker["eth1GetBlocksBatchSizeDynamic"]).toBe(expectedSize); @@ -66,7 +66,7 @@ describe("Eth1DepositDataTracker", function () { expect(expectedSize).toBe(1000); }); - it("Should dynamically adjust logs batch size", async function () { + it("Should dynamically adjust logs batch size", async () => { let expectedSize = 1000; expect(eth1DepositDataTracker["eth1GetLogsBatchSizeDynamic"]).toBe(expectedSize); diff --git a/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts b/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts index 151f3931cfde..c7f4c8fb7aa4 100644 --- a/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts +++ b/packages/beacon-node/test/unit/eth1/eth1MergeBlockTracker.test.ts @@ -8,8 +8,6 @@ import {Eth1MergeBlockTracker, StatusCode, toPowBlock} from "../../../src/eth1/e import {Eth1ProviderState, EthJsonRpcBlockRaw} from "../../../src/eth1/interface.js"; import {testLogger} from "../../utils/logger.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - describe("eth1 / Eth1MergeBlockTracker", () => { const logger = testLogger(); @@ -18,9 +16,7 @@ describe("eth1 / Eth1MergeBlockTracker", () => { let controller: AbortController; beforeEach(() => { controller = new AbortController(); - }); - afterEach(() => controller.abort()); - beforeEach(() => { + config = { // Set time units to 0 to make the test as fast as possible SECONDS_PER_ETH1_BLOCK: 0, @@ -31,6 +27,8 @@ describe("eth1 / Eth1MergeBlockTracker", () => { } as Partial as ChainConfig; }); + afterEach(() => controller.abort()); + it("Should find terminal pow block through TERMINAL_BLOCK_HASH", async () => { config.TERMINAL_BLOCK_HASH = Buffer.alloc(32, 1); const block: EthJsonRpcBlockRaw = { @@ -117,9 +115,8 @@ describe("eth1 / Eth1MergeBlockTracker", () => { if (blockNumber === "latest") { if (latestBlockPointer >= blocks.length) { throw Error("Fetched too many blocks"); - } else { - return blocks[latestBlockPointer++]; } + return blocks[latestBlockPointer++]; } return blocks[blockNumber]; }, diff --git a/packages/beacon-node/test/unit/eth1/utils/depositContract.test.ts b/packages/beacon-node/test/unit/eth1/utils/depositContract.test.ts index e36fc865a75a..22ca95765a9a 100644 --- a/packages/beacon-node/test/unit/eth1/utils/depositContract.test.ts +++ b/packages/beacon-node/test/unit/eth1/utils/depositContract.test.ts @@ -2,7 +2,7 @@ import {describe, it, expect} from "vitest"; import {goerliTestnetLogs, goerliTestnetDepositEvents} from "../../../utils/testnet.js"; import {parseDepositLog} from "../../../../src/eth1/utils/depositContract.js"; -describe("eth1 / util / depositContract", function () { +describe("eth1 / util / depositContract", () => { it("Should parse a raw deposit log", () => { const depositEvents = goerliTestnetLogs.map((log) => parseDepositLog(log)); expect(depositEvents).toEqual(goerliTestnetDepositEvents); diff --git a/packages/beacon-node/test/unit/eth1/utils/deposits.test.ts b/packages/beacon-node/test/unit/eth1/utils/deposits.test.ts index 316f75efc5ce..34334d1b3f8b 100644 --- a/packages/beacon-node/test/unit/eth1/utils/deposits.test.ts +++ b/packages/beacon-node/test/unit/eth1/utils/deposits.test.ts @@ -11,7 +11,7 @@ import {getDeposits, getDepositsWithProofs, DepositGetter} from "../../../../src import {DepositTree} from "../../../../src/db/repositories/depositDataRoot.js"; import {createCachedBeaconStateTest} from "../../../utils/cachedBeaconState.js"; -describe("eth1 / util / deposits", function () { +describe("eth1 / util / deposits", () => { describe("getDeposits", () => { type TestCase = { id: string; @@ -99,7 +99,6 @@ describe("eth1 / util / deposits", function () { }, ]; - /* eslint-disable @typescript-eslint/naming-convention */ const postElectraConfig = createChainForkConfig({ ALTAIR_FORK_EPOCH: 1, BELLATRIX_FORK_EPOCH: 2, @@ -112,7 +111,7 @@ describe("eth1 / util / deposits", function () { for (const testCase of testCases) { const {id, depositIndexes, eth1DepositIndex, depositCount, expectedReturnedIndexes, error, postElectra} = testCase; - it(id, async function () { + it(id, async () => { const state = postElectra ? generateState({slot: postElectraSlot, eth1DepositIndex}, postElectraConfig) : generateState({eth1DepositIndex}); @@ -140,7 +139,7 @@ describe("eth1 / util / deposits", function () { }); describe("getDepositsWithProofs", () => { - it("return empty array if no pending deposits", function () { + it("return empty array if no pending deposits", () => { const initialValues = [Buffer.alloc(32)]; const depositRootTree = ssz.phase0.DepositDataRootList.toViewDU(initialValues); const depositCount = 0; @@ -150,7 +149,7 @@ describe("eth1 / util / deposits", function () { expect(deposits).toEqual([]); }); - it("return deposits with valid proofs", function () { + it("return deposits with valid proofs", () => { const depositEvents = Array.from( {length: 2}, (_, index): phase0.DepositEvent => ({ diff --git a/packages/beacon-node/test/unit/eth1/utils/eth1Data.test.ts b/packages/beacon-node/test/unit/eth1/utils/eth1Data.test.ts index 4b5bf74772b7..dff98500b293 100644 --- a/packages/beacon-node/test/unit/eth1/utils/eth1Data.test.ts +++ b/packages/beacon-node/test/unit/eth1/utils/eth1Data.test.ts @@ -12,7 +12,7 @@ import {expectRejectedWithLodestarError} from "../../../utils/errors.js"; import {Eth1ErrorCode} from "../../../../src/eth1/errors.js"; import {DepositTree} from "../../../../src/db/repositories/depositDataRoot.js"; -describe("eth1 / util / getEth1DataForBlocks", function () { +describe("eth1 / util / getEth1DataForBlocks", () => { type TestCase = { id: string; blocks: Eth1Block[]; @@ -95,7 +95,7 @@ describe("eth1 / util / getEth1DataForBlocks", function () { for (const testCase of testCases) { const {id, blocks, deposits, depositRootTree, lastProcessedDepositBlockNumber, expectedEth1Data, error} = testCase(); - it(id, async function () { + it(id, async () => { const eth1DatasPromise = getEth1DataForBlocks( blocks, // Simulate a descending stream reading from DB @@ -117,7 +117,7 @@ describe("eth1 / util / getEth1DataForBlocks", function () { } }); -describe("eth1 / util / getDepositsByBlockNumber", function () { +describe("eth1 / util / getDepositsByBlockNumber", () => { type TestCase = { id: string; fromBlock: number; @@ -181,7 +181,7 @@ describe("eth1 / util / getDepositsByBlockNumber", function () { for (const testCase of testCases) { const {id, fromBlock, toBlock, deposits, expectedResult} = testCase(); - it(id, async function () { + it(id, async () => { const result = await getDepositsByBlockNumber( fromBlock, toBlock, // Simulate a descending stream reading from DB @@ -192,7 +192,7 @@ describe("eth1 / util / getDepositsByBlockNumber", function () { } }); -describe("eth1 / util / getDepositRootByDepositCount", function () { +describe("eth1 / util / getDepositRootByDepositCount", () => { type TestCase = { id: string; depositCounts: number[]; @@ -243,7 +243,7 @@ describe("eth1 / util / getDepositRootByDepositCount", function () { for (const testCase of testCases) { const {id, depositCounts, depositRootTree, expectedMap} = testCase(); - it(id, function () { + it(id, () => { const map = getDepositRootByDepositCount(depositCounts, depositRootTree); expect(renderDepositRootByDepositCount(map)).toEqual(renderDepositRootByDepositCount(expectedMap)); }); diff --git a/packages/beacon-node/test/unit/eth1/utils/eth1DepositEvent.test.ts b/packages/beacon-node/test/unit/eth1/utils/eth1DepositEvent.test.ts index 7538dc0acf63..ea504464778c 100644 --- a/packages/beacon-node/test/unit/eth1/utils/eth1DepositEvent.test.ts +++ b/packages/beacon-node/test/unit/eth1/utils/eth1DepositEvent.test.ts @@ -1,7 +1,7 @@ import {describe, it, expect} from "vitest"; import {assertConsecutiveDeposits} from "../../../../src/eth1/utils/eth1DepositEvent.js"; -describe("eth1 / util / assertConsecutiveDeposits", function () { +describe("eth1 / util / assertConsecutiveDeposits", () => { const testCases: { id: string; ok: boolean; diff --git a/packages/beacon-node/test/unit/eth1/utils/eth1Vote.test.ts b/packages/beacon-node/test/unit/eth1/utils/eth1Vote.test.ts index 0ad63e5e0d8f..e9a9ab5aad24 100644 --- a/packages/beacon-node/test/unit/eth1/utils/eth1Vote.test.ts +++ b/packages/beacon-node/test/unit/eth1/utils/eth1Vote.test.ts @@ -12,7 +12,7 @@ import { Eth1DataGetter, } from "../../../../src/eth1/utils/eth1Vote.js"; -describe("eth1 / util / eth1Vote", function () { +describe("eth1 / util / eth1Vote", () => { function generateEth1Vote(i: number): phase0.Eth1Data { return { blockHash: Buffer.alloc(32, i), @@ -21,7 +21,7 @@ describe("eth1 / util / eth1Vote", function () { }; } - describe("pickEth1Vote", function () { + describe("pickEth1Vote", () => { // Function array to scope votes in each test case defintion const testCases: (() => { id: string; @@ -82,7 +82,7 @@ describe("eth1 / util / eth1Vote", function () { for (const testCase of testCases) { const {id, eth1DataVotesInState, votesToConsider, expectedEth1Vote} = testCase(); - it(id, async function () { + it(id, async () => { const state = generateState({slot: 5, eth1DataVotes: eth1DataVotesInState}); const eth1Vote = pickEth1Vote(state, votesToConsider); expect(ssz.phase0.Eth1Data.toJson(eth1Vote)).toEqual(ssz.phase0.Eth1Data.toJson(expectedEth1Vote)); @@ -90,7 +90,7 @@ describe("eth1 / util / eth1Vote", function () { } }); - describe("getEth1VotesToConsider", function () { + describe("getEth1VotesToConsider", () => { // Function array to scope votes in each test case defintion const testCases: (() => { id: string; @@ -127,7 +127,7 @@ describe("eth1 / util / eth1Vote", function () { for (const testCase of testCases) { const {id, state, eth1Datas, expectedVotesToConsider} = testCase(); - it(`get votesToConsider: ${id}`, async function () { + it(`get votesToConsider: ${id}`, async () => { const eth1DataGetter: Eth1DataGetter = async ({timestampRange}) => filterBy(eth1Datas, timestampRange, (eth1Data) => eth1Data.timestamp); diff --git a/packages/beacon-node/test/unit/eth1/utils/groupDepositEventsByBlock.test.ts b/packages/beacon-node/test/unit/eth1/utils/groupDepositEventsByBlock.test.ts index 5712d1095270..a4e786b3aa68 100644 --- a/packages/beacon-node/test/unit/eth1/utils/groupDepositEventsByBlock.test.ts +++ b/packages/beacon-node/test/unit/eth1/utils/groupDepositEventsByBlock.test.ts @@ -2,7 +2,7 @@ import {describe, it, expect} from "vitest"; import {phase0} from "@lodestar/types"; import {groupDepositEventsByBlock} from "../../../../src/eth1/utils/groupDepositEventsByBlock.js"; -describe("eth1 / util / groupDepositEventsByBlock", function () { +describe("eth1 / util / groupDepositEventsByBlock", () => { it("should return deposit events by block sorted by index", () => { const depositData = { amount: 0, diff --git a/packages/beacon-node/test/unit/eth1/utils/optimizeNextBlockDiffForGenesis.test.ts b/packages/beacon-node/test/unit/eth1/utils/optimizeNextBlockDiffForGenesis.test.ts index cacfd7dc685f..39c4a4d6e773 100644 --- a/packages/beacon-node/test/unit/eth1/utils/optimizeNextBlockDiffForGenesis.test.ts +++ b/packages/beacon-node/test/unit/eth1/utils/optimizeNextBlockDiffForGenesis.test.ts @@ -2,14 +2,11 @@ import {describe, it, expect} from "vitest"; import {optimizeNextBlockDiffForGenesis} from "../../../../src/eth1/utils/optimizeNextBlockDiffForGenesis.js"; import {Eth1Block} from "../../../../src/eth1/interface.js"; -describe("eth1 / utils / optimizeNextBlockDiffForGenesis", function () { +describe("eth1 / utils / optimizeNextBlockDiffForGenesis", () => { it("should return optimized block diff to find genesis time", () => { const params = { - // eslint-disable-next-line @typescript-eslint/naming-convention MIN_GENESIS_TIME: 1578009600, - // eslint-disable-next-line @typescript-eslint/naming-convention GENESIS_DELAY: 172800, - // eslint-disable-next-line @typescript-eslint/naming-convention SECONDS_PER_ETH1_BLOCK: 14, }; const initialTimeDiff = params.GENESIS_DELAY * 2; @@ -32,9 +29,8 @@ describe("eth1 / utils / optimizeNextBlockDiffForGenesis", function () { if (lastFetchedBlock.timestamp > params.MIN_GENESIS_TIME - params.GENESIS_DELAY) { break; - } else { - diffRecord.push({number: lastFetchedBlock.blockNumber, blockDiff}); } + diffRecord.push({number: lastFetchedBlock.blockNumber, blockDiff}); } // Make sure the returned diffs converge to genesis time fast diff --git a/packages/beacon-node/test/unit/executionEngine/http.test.ts b/packages/beacon-node/test/unit/executionEngine/http.test.ts index 5ac4fd4ca670..c9f4ae671e53 100644 --- a/packages/beacon-node/test/unit/executionEngine/http.test.ts +++ b/packages/beacon-node/test/unit/executionEngine/http.test.ts @@ -193,9 +193,6 @@ describe("ExecutionEngine / http", () => { amount: "0x7b", }, ], - depositRequests: null, // depositRequests is null pre-electra - withdrawalRequests: null, - consolidationRequests: null, }, null, // null returned for missing blocks { @@ -204,9 +201,6 @@ describe("ExecutionEngine / http", () => { "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", ], withdrawals: null, // withdrawals is null pre-capella - depositRequests: null, // depositRequests is null pre-electra - withdrawalRequests: null, - consolidationRequests: null, }, ], }; @@ -254,9 +248,6 @@ describe("ExecutionEngine / http", () => { amount: "0x7b", }, ], - depositRequests: null, // depositRequests is null pre-electra - withdrawalRequests: null, - consolidationRequests: null, }, null, // null returned for missing blocks { @@ -265,9 +256,6 @@ describe("ExecutionEngine / http", () => { "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", ], withdrawals: null, // withdrawals is null pre-capella - depositRequests: null, // depositRequests is null pre-electra - withdrawalRequests: null, - consolidationRequests: null, }, ], }; diff --git a/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts b/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts index b75dc8048283..b254fc9d8b45 100644 --- a/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts +++ b/packages/beacon-node/test/unit/executionEngine/httpRetry.test.ts @@ -32,10 +32,10 @@ describe("ExecutionEngine / http ", () => { reqJsonRpcPayload = req.body; delete (reqJsonRpcPayload as {id?: number}).id; return returnValue; - } else { - --errorResponsesBeforeSuccess; - throw Error(`Will succeed after ${errorResponsesBeforeSuccess} more attempts`); } + + --errorResponsesBeforeSuccess; + throw Error(`Will succeed after ${errorResponsesBeforeSuccess} more attempts`); }); afterCallbacks.push(async () => { @@ -56,8 +56,8 @@ describe("ExecutionEngine / http ", () => { ); }); - describe("notifyForkchoiceUpdate", function () { - it("notifyForkchoiceUpdate no retry when no pay load attributes", async function () { + describe("notifyForkchoiceUpdate", () => { + it("notifyForkchoiceUpdate no retry when no pay load attributes", async () => { errorResponsesBeforeSuccess = 2; const forkChoiceHeadData = { headBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", @@ -85,7 +85,7 @@ describe("ExecutionEngine / http ", () => { expect(errorResponsesBeforeSuccess).toBe(1); }); - it("notifyForkchoiceUpdate with retry when pay load attributes", async function () { + it("notifyForkchoiceUpdate with retry when pay load attributes", async () => { errorResponsesBeforeSuccess = defaultExecutionEngineHttpOpts.retries - 1; const forkChoiceHeadData = { headBlockHash: "0xb084c10440f05f5a23a55d1d7ebcb1b3892935fb56f23cdc9a7f42c348eed174", diff --git a/packages/beacon-node/test/unit/monitoring/remoteService.ts b/packages/beacon-node/test/unit/monitoring/remoteService.ts index c5ce0e9ef0b3..d407adb9d175 100644 --- a/packages/beacon-node/test/unit/monitoring/remoteService.ts +++ b/packages/beacon-node/test/unit/monitoring/remoteService.ts @@ -22,7 +22,7 @@ export const remoteServiceError: RemoteServiceError = {status: "error", data: nu export async function startRemoteService(): Promise<{baseUrl: URL}> { const server = fastify(); - server.post(remoteServiceRoutes.success, {}, async function (request, reply) { + server.post(remoteServiceRoutes.success, {}, async (request, reply) => { if (Array.isArray(request.body)) { request.body.forEach(validateRequestData); } else { @@ -32,11 +32,9 @@ export async function startRemoteService(): Promise<{baseUrl: URL}> { return reply.status(200).send(); }); - server.post(remoteServiceRoutes.error, {}, async function (_request, reply) { - return reply.status(400).send(remoteServiceError); - }); + server.post(remoteServiceRoutes.error, {}, async (_request, reply) => reply.status(400).send(remoteServiceError)); - server.post(remoteServiceRoutes.pending, {}, function () { + server.post(remoteServiceRoutes.pending, {}, () => { // keep request pending until timeout is reached or aborted }); @@ -74,13 +72,13 @@ function validateRequestData(data: ReceivedData): void { } function validateClientStats(data: ReceivedData, schema: ClientStatsSchema): void { - schema.forEach((s) => { + for (const s of schema) { try { expect(data[s.key]).toBeInstanceOf(s.type); - } catch { + } catch (_e) { throw new Error( `Validation of property "${s.key}" failed. Expected type "${s.type}" but received "${typeof data[s.key]}".` ); } - }); + } } diff --git a/packages/beacon-node/test/unit/monitoring/service.test.ts b/packages/beacon-node/test/unit/monitoring/service.test.ts index 43936ff1756d..fed64b9bc553 100644 --- a/packages/beacon-node/test/unit/monitoring/service.test.ts +++ b/packages/beacon-node/test/unit/monitoring/service.test.ts @@ -108,7 +108,6 @@ describe("monitoring / service", () => { expect(logger.info).toHaveBeenCalledWith( "Started monitoring service", // TODO: Debug why `expect.any` causing type error - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({interval: expect.any(Number), machine: null, remote: expect.any(String)}) ); }); @@ -170,7 +169,7 @@ describe("monitoring / service", () => { service?.close(); }); - (["beacon", "validator"] as const).forEach((client) => { + for (const client of ["beacon", "validator"] as const) { it(`should collect and send ${client} stats to remote service`, async () => { const endpoint = `${baseUrl}${remoteServiceRoutes.success}`; service = new MonitoringService(client, {endpoint, collectSystemStats: true}, {register, logger}); @@ -182,7 +181,7 @@ describe("monitoring / service", () => { // Fail test if warning was logged due to a 500 response. expect(logger.warn).not.toHaveBeenCalledWith("Failed to send client stats"); }); - }); + } it("should properly handle remote service errors", async () => { const endpoint = `${baseUrl}${remoteServiceRoutes.error}`; diff --git a/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts b/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts index d1dc7ba57fa9..2104235e7215 100644 --- a/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts +++ b/packages/beacon-node/test/unit/network/beaconBlocksMaybeBlobsByRange.test.ts @@ -10,14 +10,13 @@ import {INetwork} from "../../../src/network/interface.js"; import {ZERO_HASH} from "../../../src/constants/constants.js"; describe("beaconBlocksMaybeBlobsByRange", () => { - beforeAll(async function () { + beforeAll(async () => { await initCKZG(); loadEthereumTrustedSetup(); }); const peerId = "Qma9T5YraSnpRDZqRR4krcSJabThc8nwZuJV3LercPHufi"; - /* eslint-disable @typescript-eslint/naming-convention */ const chainConfig = createChainForkConfig({ ...defaultChainConfig, ALTAIR_FORK_EPOCH: 0, diff --git a/packages/beacon-node/test/unit/network/gossip/topic.test.ts b/packages/beacon-node/test/unit/network/gossip/topic.test.ts index dbaa4002bfcc..2a61d8604439 100644 --- a/packages/beacon-node/test/unit/network/gossip/topic.test.ts +++ b/packages/beacon-node/test/unit/network/gossip/topic.test.ts @@ -4,7 +4,7 @@ import {GossipType, GossipEncoding, GossipTopicMap} from "../../../../src/networ import {parseGossipTopic, stringifyGossipTopic} from "../../../../src/network/gossip/topic.js"; import {config} from "../../../utils/config.js"; -describe("network / gossip / topic", function () { +describe("network / gossip / topic", () => { const encoding = GossipEncoding.ssz_snappy; // Enforce with Typescript that we test all GossipType diff --git a/packages/beacon-node/test/unit/network/metadata.test.ts b/packages/beacon-node/test/unit/network/metadata.test.ts index 12bd9b168425..50e4157a29cd 100644 --- a/packages/beacon-node/test/unit/network/metadata.test.ts +++ b/packages/beacon-node/test/unit/network/metadata.test.ts @@ -4,7 +4,7 @@ import {ssz} from "@lodestar/types"; import {getENRForkID} from "../../../src/network/metadata.js"; import {config} from "../../utils/config.js"; -describe("network / metadata / getENRForkID", function () { +describe("network / metadata / getENRForkID", () => { // At 0, next fork is altair const currentEpoch = 0; const enrForkID = getENRForkID(config, currentEpoch); diff --git a/packages/beacon-node/test/unit/network/peers/priorization.test.ts b/packages/beacon-node/test/unit/network/peers/priorization.test.ts index 3ec770051275..26f8d8c9e535 100644 --- a/packages/beacon-node/test/unit/network/peers/priorization.test.ts +++ b/packages/beacon-node/test/unit/network/peers/priorization.test.ts @@ -14,7 +14,6 @@ import {RequestedSubnet} from "../../../../src/network/peers/utils/index.js"; type Result = ReturnType; -// eslint-disable-next-line vitest/valid-describe-callback describe("network / peers / priorization", async () => { const peers: PeerId[] = []; for (let i = 0; i < 8; i++) { @@ -264,8 +263,7 @@ describe("network / peers / priorization", async () => { } }); -// eslint-disable-next-line vitest/valid-describe-callback -describe("sortPeersToPrune", async function () { +describe("sortPeersToPrune", async () => { const peers: PeerId[] = []; for (let i = 0; i < 8; i++) { const peer = await createSecp256k1PeerId(); diff --git a/packages/beacon-node/test/unit/network/peers/score.test.ts b/packages/beacon-node/test/unit/network/peers/score.test.ts index df842f9a1310..8962b282e0ad 100644 --- a/packages/beacon-node/test/unit/network/peers/score.test.ts +++ b/packages/beacon-node/test/unit/network/peers/score.test.ts @@ -19,19 +19,18 @@ vi.mock("../../../../src/network/peers/score/index.js", async (importActual) => }; }); -describe("simple block provider score tracking", function () { +describe("simple block provider score tracking", () => { const peer = peerIdFromString("Qma9T5YraSnpRDZqRR4krcSJabThc8nwZuJV3LercPHufi"); const MIN_SCORE = -100; const actionName = "test-action"; - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type function mockStore() { const scoreStore = new PeerRpcScoreStore(); const peerScores = scoreStore["scores"] as MapDef; return {scoreStore, peerScores}; } - it("Should return default score, without any previous action", function () { + it("Should return default score, without any previous action", () => { const {scoreStore} = mockStore(); const score = scoreStore.getScore(peer); expect(score).toBe(0); @@ -70,7 +69,7 @@ describe("simple block provider score tracking", function () { expect(scoreStore.getScore(peer)).toBeGreaterThan(minScore); }); - it("should not go below min score", function () { + it("should not go below min score", () => { const {scoreStore} = mockStore(); scoreStore.applyAction(peer, PeerAction.Fatal, actionName); scoreStore.applyAction(peer, PeerAction.Fatal, actionName); @@ -78,7 +77,7 @@ describe("simple block provider score tracking", function () { }); }); -describe("updateGossipsubScores", function () { +describe("updateGossipsubScores", () => { let peerRpcScoresStub: PeerRpcScoreStore; beforeEach(() => { diff --git a/packages/beacon-node/test/unit/network/processorQueues.test.ts b/packages/beacon-node/test/unit/network/processorQueues.test.ts index 378d87ab7861..07a10591c0ad 100644 --- a/packages/beacon-node/test/unit/network/processorQueues.test.ts +++ b/packages/beacon-node/test/unit/network/processorQueues.test.ts @@ -21,10 +21,10 @@ async function validateTest(job: string, tracker: string[], opts: ValidateOpts): async function getStateFromCache(retrieveSync: boolean): Promise { if (retrieveSync) { return 1; - } else { - await sleep(0); - return 2; } + + await sleep(0); + return 2; } describe("event loop with branching async", () => { diff --git a/packages/beacon-node/test/unit/network/subnets/attnetsService.test.ts b/packages/beacon-node/test/unit/network/subnets/attnetsService.test.ts index 0c7b3df98268..d279a5d759d7 100644 --- a/packages/beacon-node/test/unit/network/subnets/attnetsService.test.ts +++ b/packages/beacon-node/test/unit/network/subnets/attnetsService.test.ts @@ -26,7 +26,6 @@ describe("AttnetsService", () => { "be" ); const ALTAIR_FORK_EPOCH = 100; - // eslint-disable-next-line @typescript-eslint/naming-convention const config = createBeaconConfig({ALTAIR_FORK_EPOCH}, ZERO_HASH); // const {SECONDS_PER_SLOT} = config; let service: AttnetsService; @@ -36,7 +35,7 @@ describe("AttnetsService", () => { let clock: IClock; const logger = testLogger(); - beforeEach(function () { + beforeEach(() => { vi.useFakeTimers({now: Date.now()}); gossipStub = vi.mocked(new Eth2Gossipsub({} as any, {} as any)); vi.spyOn(gossipStub, "subscribeTopic").mockReturnValue(undefined); diff --git a/packages/beacon-node/test/unit/network/util.test.ts b/packages/beacon-node/test/unit/network/util.test.ts index 14c74930e521..70da41bd2c2a 100644 --- a/packages/beacon-node/test/unit/network/util.test.ts +++ b/packages/beacon-node/test/unit/network/util.test.ts @@ -4,7 +4,7 @@ import {ForkName} from "@lodestar/params"; import {getDiscv5Multiaddrs} from "../../../src/network/libp2p/index.js"; import {getCurrentAndNextFork} from "../../../src/network/forks.js"; -describe("getCurrentAndNextFork", function () { +describe("getCurrentAndNextFork", () => { const altairEpoch = config.forks.altair.epoch; afterEach(() => { config.forks.altair.epoch = altairEpoch; @@ -34,7 +34,7 @@ describe("getCurrentAndNextFork", function () { }); describe("getDiscv5Multiaddrs", () => { - it("should extract bootMultiaddrs from enr with tcp", async function () { + it("should extract bootMultiaddrs from enr with tcp", async () => { const enrWithTcp = [ "enr:-LK4QDiPGwNomqUqNDaM3iHYvtdX7M5qngson6Qb2xGIg1LwC8-Nic0aQwO0rVbJt5xp32sRE3S1YqvVrWO7OgVNv0kBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpA7CIeVAAAgCf__________gmlkgnY0gmlwhBKNA4qJc2VjcDI1NmsxoQKbBS4ROQ_sldJm5tMgi36qm5I5exKJFb4C8dDVS_otAoN0Y3CCIyiDdWRwgiMo", ]; @@ -45,7 +45,7 @@ describe("getDiscv5Multiaddrs", () => { ); }); - it("should not extract bootMultiaddrs from enr without tcp", async function () { + it("should not extract bootMultiaddrs from enr without tcp", async () => { const enrWithoutTcp = [ "enr:-Ku4QCFQW96tEDYPjtaueW3WIh1CB0cJnvw_ibx5qIFZGqfLLj-QajMX6XwVs2d4offuspwgH3NkIMpWtCjCytVdlywGh2F0dG5ldHOIEAIAAgABAUyEZXRoMpCi7FS9AQAAAAAiAQAAAAAAgmlkgnY0gmlwhFA4VK6Jc2VjcDI1NmsxoQNGH1sJJS86-0x9T7qQewz9Wn9zlp6bYxqqrR38JQ49yIN1ZHCCIyg", ]; diff --git a/packages/beacon-node/test/unit/sync/backfill/verify.test.ts b/packages/beacon-node/test/unit/sync/backfill/verify.test.ts index 08ffdc4c3b02..3197013455e3 100644 --- a/packages/beacon-node/test/unit/sync/backfill/verify.test.ts +++ b/packages/beacon-node/test/unit/sync/backfill/verify.test.ts @@ -4,25 +4,23 @@ import {fileURLToPath} from "node:url"; import {describe, it, expect} from "vitest"; import {createBeaconConfig} from "@lodestar/config"; import {config} from "@lodestar/config/default"; -import {phase0, ssz} from "@lodestar/types"; +import {phase0, ssz, WithBytes} from "@lodestar/types"; import {verifyBlockSequence} from "../../../../src/sync/backfill/verify.js"; -import {WithBytes} from "../../../../src/network/interface.js"; import {ZERO_HASH} from "../../../../src/constants/constants.js"; import {BackfillSyncErrorCode, BackfillSyncError} from "./../../../../src/sync/backfill/errors.js"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); -describe("backfill sync - verify block sequence", function () { +describe("backfill sync - verify block sequence", () => { //mainnet validators root const beaconConfig = createBeaconConfig( config, ssz.Root.fromJson("0x4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95") ); - it("should verify valid chain of blocks", function () { + it("should verify valid chain of blocks", () => { const blocks = getBlocks(); expect(() => @@ -30,20 +28,22 @@ describe("backfill sync - verify block sequence", function () { ).not.toThrow(); }); - it("should fail with sequence not anchored", function () { + it("should fail with sequence not anchored", () => { const blocks = getBlocks(); const wrongAncorRoot = ssz.Root.defaultValue(); expect(() => verifyBlockSequence(beaconConfig, blocks, wrongAncorRoot)).toThrow(BackfillSyncErrorCode.NOT_ANCHORED); }); - it("should fail with sequence not linear", function () { + it("should fail with sequence not linear", () => { const blocks = getBlocks(); expect(() => { const {error} = verifyBlockSequence( beaconConfig, // remove middle block - blocks.filter((b) => b.data.message.slot !== 2).slice(0, blocks.length - 2), + blocks + .filter((b) => b.data.message.slot !== 2) + .slice(0, blocks.length - 2), blocks[blocks.length - 1].data.message.parentRoot ); if (error != null) throw new BackfillSyncError({code: error}); 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 44d5cc7c173a..658f950b2c18 100644 --- a/packages/beacon-node/test/unit/sync/range/chain.test.ts +++ b/packages/beacon-node/test/unit/sync/range/chain.test.ts @@ -72,7 +72,7 @@ describe("sync / range / chain", () => { } }; - const downloadBeaconBlocksByRange: SyncChainFns["downloadBeaconBlocksByRange"] = async (peerId, request) => { + const downloadBeaconBlocksByRange: SyncChainFns["downloadBeaconBlocksByRange"] = async (_peerId, request) => { const blocks: BlockInput[] = []; for (let i = request.startSlot; i < request.startSlot + request.count; i += request.step) { if (skippedSlots?.has(i)) { @@ -124,7 +124,7 @@ describe("sync / range / chain", () => { const peers = [peer]; const processChainSegment: SyncChainFns["processChainSegment"] = async () => {}; - const downloadBeaconBlocksByRange: SyncChainFns["downloadBeaconBlocksByRange"] = async (peer, request) => { + const downloadBeaconBlocksByRange: SyncChainFns["downloadBeaconBlocksByRange"] = async (_peer, request) => { const blocks: BlockInput[] = []; for (let i = request.startSlot; i < request.startSlot + request.count; i += request.step) { blocks.push( diff --git a/packages/beacon-node/test/unit/sync/unknownBlock.test.ts b/packages/beacon-node/test/unit/sync/unknownBlock.test.ts index f6791fc29e9c..0694cb97f4ab 100644 --- a/packages/beacon-node/test/unit/sync/unknownBlock.test.ts +++ b/packages/beacon-node/test/unit/sync/unknownBlock.test.ts @@ -22,7 +22,6 @@ import {ZERO_HASH} from "../../../src/constants/constants.js"; describe("sync by UnknownBlockSync", () => { const logger = testLogger(); const slotSec = 0.3; - // eslint-disable-next-line @typescript-eslint/naming-convention const config = createChainForkConfig({...minimalConfig, SECONDS_PER_SLOT: slotSec}); beforeEach(() => { @@ -119,11 +118,13 @@ describe("sync by UnknownBlockSync", () => { ]); let reportPeerResolveFn: (value: Parameters) => void; - const reportPeerPromise = new Promise>((r) => (reportPeerResolveFn = r)); + const reportPeerPromise = new Promise>((r) => { + reportPeerResolveFn = r; + }); let sendBeaconBlocksByRootResolveFn: (value: Parameters) => void; - const sendBeaconBlocksByRootPromise = new Promise>( - (r) => (sendBeaconBlocksByRootResolveFn = r) - ); + const sendBeaconBlocksByRootPromise = new Promise>((r) => { + sendBeaconBlocksByRootResolveFn = r; + }); const network: Partial = { events: new NetworkEventBus(), @@ -157,8 +158,12 @@ describe("sync by UnknownBlockSync", () => { let blockAResolver: () => void; let blockCResolver: () => void; - const blockAProcessed = new Promise((resolve) => (blockAResolver = resolve)); - const blockCProcessed = new Promise((resolve) => (blockCResolver = resolve)); + const blockAProcessed = new Promise((resolve) => { + blockAResolver = resolve; + }); + const blockCProcessed = new Promise((resolve) => { + blockCResolver = resolve; + }); const chain: Partial = { clock: new ClockStopped(0), @@ -234,7 +239,7 @@ describe("sync by UnknownBlockSync", () => { } }); -describe("UnknownBlockSync", function () { +describe("UnknownBlockSync", () => { let network: INetwork; let chain: MockedBeaconChain; const logger = testLogger(); diff --git a/packages/beacon-node/test/unit/util/array.test.ts b/packages/beacon-node/test/unit/util/array.test.ts index d505d27c2e9f..75d3a1b0856d 100644 --- a/packages/beacon-node/test/unit/util/array.test.ts +++ b/packages/beacon-node/test/unit/util/array.test.ts @@ -3,13 +3,13 @@ import {findLastIndex, LinkedList} from "../../../src/util/array.js"; describe("findLastIndex", () => { it("should return the last index that matches a predicate", () => { - expect(findLastIndex([1, 2, 3, 4], (n) => n % 2 == 0)).toEqual(3); - expect(findLastIndex([1, 2, 3, 4, 5], (n) => n % 2 == 0)).toEqual(3); + expect(findLastIndex([1, 2, 3, 4], (n) => n % 2 === 0)).toEqual(3); + expect(findLastIndex([1, 2, 3, 4, 5], (n) => n % 2 === 0)).toEqual(3); expect(findLastIndex([1, 2, 3, 4, 5], () => true)).toEqual(4); }); it("should return -1 if there are no matches", () => { - expect(findLastIndex([1, 3, 5], (n) => n % 2 == 0)).toEqual(-1); + expect(findLastIndex([1, 3, 5], (n) => n % 2 === 0)).toEqual(-1); expect(findLastIndex([1, 2, 3, 4, 5], () => false)).toEqual(-1); }); }); diff --git a/packages/beacon-node/test/unit/util/clock.test.ts b/packages/beacon-node/test/unit/util/clock.test.ts index ff224c1b378a..87955b98182c 100644 --- a/packages/beacon-node/test/unit/util/clock.test.ts +++ b/packages/beacon-node/test/unit/util/clock.test.ts @@ -4,7 +4,7 @@ import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {Clock, ClockEvent} from "../../../src/util/clock.js"; import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../../src/constants/index.js"; -describe("Clock", function () { +describe("Clock", () => { let abortController: AbortController; let clock: Clock; @@ -26,7 +26,7 @@ describe("Clock", function () { }); // TODO: Debug why this test is fragile after migrating to vitest - it.skip("Should notify on new slot", function () { + it.skip("Should notify on new slot", () => { const spy = vi.fn(); clock.on(ClockEvent.slot, spy); vi.advanceTimersByTime(config.SECONDS_PER_SLOT * 1000); @@ -34,7 +34,7 @@ describe("Clock", function () { expect(spy).toBeCalledWith(clock.currentSlot); }); - it("Should notify on new epoch", function () { + it("Should notify on new epoch", () => { const spy = vi.fn(); clock.on(ClockEvent.epoch, spy); vi.advanceTimersByTime(SLOTS_PER_EPOCH * config.SECONDS_PER_SLOT * 1000); diff --git a/packages/beacon-node/test/unit/util/dependentRoot.test.ts b/packages/beacon-node/test/unit/util/dependentRoot.test.ts index e7923f111d0b..c8044f332e34 100644 --- a/packages/beacon-node/test/unit/util/dependentRoot.test.ts +++ b/packages/beacon-node/test/unit/util/dependentRoot.test.ts @@ -26,9 +26,8 @@ describe("util / getShufflingDependentRoot", () => { forkchoiceStub.getDependentRoot.mockImplementation((block, epochDiff) => { if (block === headBattHeadBlock && epochDiff === EpochDifference.previous) { return "current"; - } else { - throw new Error("should not be called"); } + throw new Error("should not be called"); }); expect(getShufflingDependentRoot(forkchoiceStub, attEpoch, blockEpoch, headBattHeadBlock)).toEqual("current"); }); @@ -39,9 +38,8 @@ describe("util / getShufflingDependentRoot", () => { forkchoiceStub.getDependentRoot.mockImplementation((block, epochDiff) => { if (block === headBattHeadBlock && epochDiff === EpochDifference.current) { return "0x000"; - } else { - throw new Error("should not be called"); } + throw new Error("should not be called"); }); expect(getShufflingDependentRoot(forkchoiceStub, attEpoch, blockEpoch, headBattHeadBlock)).toEqual("0x000"); }); diff --git a/packages/beacon-node/test/unit/util/file.test.ts b/packages/beacon-node/test/unit/util/file.test.ts index 9e519ac01681..6040e14b4263 100644 --- a/packages/beacon-node/test/unit/util/file.test.ts +++ b/packages/beacon-node/test/unit/util/file.test.ts @@ -3,10 +3,10 @@ import path from "node:path"; import {describe, it, expect, beforeAll, afterAll} from "vitest"; import {ensureDir, writeIfNotExist} from "../../../src/util/file.js"; -describe("file util", function () { +describe("file util", () => { const dirPath = path.join(".", "keys/toml/test_config.toml"); - describe("ensureDir", function () { + describe("ensureDir", () => { it("create dir if not exists", async () => { // ${dirPath} should not exist expect(fs.existsSync(dirPath)).toBe(false); @@ -17,7 +17,7 @@ describe("file util", function () { }); }); - describe("writeIfNotExist", function () { + describe("writeIfNotExist", () => { const filePath = path.join(dirPath, "test.txt"); const data = new Uint8Array([0, 1, 2]); beforeAll(async () => { diff --git a/packages/beacon-node/test/unit/util/kzg.test.ts b/packages/beacon-node/test/unit/util/kzg.test.ts index e170aed1a2de..3b1de419ae93 100644 --- a/packages/beacon-node/test/unit/util/kzg.test.ts +++ b/packages/beacon-node/test/unit/util/kzg.test.ts @@ -16,7 +16,7 @@ describe("C-KZG", () => { } }); - beforeAll(async function () { + beforeAll(async () => { await initCKZG(); loadEthereumTrustedSetup(); }); @@ -31,7 +31,6 @@ describe("C-KZG", () => { expect(ckzg.verifyBlobKzgProofBatch(blobs, commitments, proofs)).toBe(true); }); - /* eslint-disable @typescript-eslint/naming-convention */ it("BlobSidecars", async () => { const chainConfig = createChainForkConfig({ ...defaultChainConfig, @@ -64,14 +63,14 @@ describe("C-KZG", () => { // Full validation validateBlobSidecars(slot, blockRoot, kzgCommitments, blobSidecars); - blobSidecars.forEach(async (blobSidecar) => { + for (const blobSidecar of blobSidecars) { try { await validateGossipBlobSidecar(chain, blobSidecar, blobSidecar.index); - } catch (error) { + } catch (_e) { // We expect some error from here // console.log(error); } - }); + } }); }); diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index 612eea5a4388..8b72c31df6c8 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -6,7 +6,7 @@ import {ForkName, MAX_COMMITTEES_PER_SLOT} from "@lodestar/params"; import { getAttDataFromAttestationSerialized, getAttDataFromSignedAggregateAndProofPhase0, - getAggregationBitsFromAttestationSerialized as getAggregationBitsFromAttestationSerialized, + getAggregationBitsFromAttestationSerialized, getBlockRootFromAttestationSerialized, getBlockRootFromSignedAggregateAndProofSerialized, getSlotFromAttestationSerialized, diff --git a/packages/beacon-node/test/unit/util/wrapError.test.ts b/packages/beacon-node/test/unit/util/wrapError.test.ts index 19bff7321e3c..2a8e5ca15ceb 100644 --- a/packages/beacon-node/test/unit/util/wrapError.test.ts +++ b/packages/beacon-node/test/unit/util/wrapError.test.ts @@ -5,13 +5,15 @@ describe("util / wrapError", () => { const error = Error("test-error"); async function throwNoAwait(shouldThrow: boolean): Promise { if (shouldThrow) throw error; - else return true; + + return true; } async function throwAwait(shouldThrow: boolean): Promise { await new Promise((r) => setTimeout(r, 0)); if (shouldThrow) throw error; - else return true; + + return true; } it("Handle error and result with throwNoAwait", async () => { diff --git a/packages/beacon-node/test/utils/cache.ts b/packages/beacon-node/test/utils/cache.ts index 9d5d3566c99d..2fd15427c659 100644 --- a/packages/beacon-node/test/utils/cache.ts +++ b/packages/beacon-node/test/utils/cache.ts @@ -3,7 +3,7 @@ import {toHexString} from "@chainsafe/ssz"; export function memoOnce(fn: () => R): () => R { let value: R | null = null; - return function () { + return () => { if (value === null) { value = fn(); } diff --git a/packages/beacon-node/test/utils/clock.ts b/packages/beacon-node/test/utils/clock.ts index ea14c866c1b0..390d7dd095dd 100644 --- a/packages/beacon-node/test/utils/clock.ts +++ b/packages/beacon-node/test/utils/clock.ts @@ -4,11 +4,14 @@ import {Slot, Epoch} from "@lodestar/types"; import {IClock} from "../../src/util/clock.js"; export class ClockStatic extends EventEmitter implements IClock { + genesisTime: number; + constructor( readonly currentSlot: Slot, - public genesisTime = 0 + genesisTime = 0 ) { super(); + this.genesisTime = genesisTime; } get currentEpoch(): Epoch { diff --git a/packages/beacon-node/test/utils/config.ts b/packages/beacon-node/test/utils/config.ts index 2aad1c14c03e..f5d566560b65 100644 --- a/packages/beacon-node/test/utils/config.ts +++ b/packages/beacon-node/test/utils/config.ts @@ -6,7 +6,6 @@ import {ZERO_HASH} from "../../src/constants/index.js"; /** default config with ZERO_HASH as genesisValidatorsRoot */ export const config = createBeaconConfig(chainConfig, ZERO_HASH); -/* eslint-disable @typescript-eslint/naming-convention */ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig { switch (fork) { case ForkName.phase0: diff --git a/packages/beacon-node/test/utils/errors.ts b/packages/beacon-node/test/utils/errors.ts index c3d293e83c78..1bf31a2ce9af 100644 --- a/packages/beacon-node/test/utils/errors.ts +++ b/packages/beacon-node/test/utils/errors.ts @@ -53,9 +53,11 @@ export function expectLodestarError(err1: LodestarErro export function getErrorMetadata(err: LodestarError | Error | unknown): unknown { if (err instanceof LodestarError) { return mapValues(err.getMetadata(), (value) => getErrorMetadata(value as any)); - } else if (err instanceof Error) { + } + + if (err instanceof Error) { return err.message; - } else { - return err; } + + return err; } diff --git a/packages/beacon-node/test/utils/networkWithMockDb.ts b/packages/beacon-node/test/utils/networkWithMockDb.ts index f8a4a6181d2f..689d332ebbce 100644 --- a/packages/beacon-node/test/utils/networkWithMockDb.ts +++ b/packages/beacon-node/test/utils/networkWithMockDb.ts @@ -12,6 +12,7 @@ import {createCachedBeaconStateTest} from "./cachedBeaconState.js"; import {ClockStatic} from "./clock.js"; import {testLogger} from "./logger.js"; import {generateState} from "./state.js"; +import {StateArchiveMode} from "../../src/index.js"; export type NetworkForTestOpts = { startSlot?: number; @@ -54,6 +55,7 @@ export async function getNetworkForTest( disableLightClientServerOnImportBlockHead: true, disablePrepareNextSlot: true, minSameMessageSignatureSetsToBatch: 32, + stateArchiveMode: StateArchiveMode.Frequency, }, { config: beaconConfig, diff --git a/packages/beacon-node/test/utils/node/simTest.ts b/packages/beacon-node/test/utils/node/simTest.ts index e94c32cefb10..7b771b8534ed 100644 --- a/packages/beacon-node/test/utils/node/simTest.ts +++ b/packages/beacon-node/test/utils/node/simTest.ts @@ -112,7 +112,7 @@ function avg(arr: number[]): number { /** * Print a table grid of (Y) epoch / (X) slot_per_epoch */ -function printEpochSlotGrid(map: Map, config: BeaconConfig, title: string): void { +function printEpochSlotGrid(map: Map, _config: BeaconConfig, title: string): void { const lastSlot = Array.from(map.keys())[map.size - 1]; const lastEpoch = computeEpochAtSlot(lastSlot); const rowsByEpochBySlot = linspace(0, lastEpoch).map((epoch) => { @@ -132,7 +132,7 @@ function printEpochGrid(maps: Record>, title: string) return epoch > max ? epoch : max; }, 0); const epochGrid = linspace(0, lastEpoch).map((epoch) => - mapValues(maps, (val, key) => formatValue(maps[key].get(epoch))) + mapValues(maps, (_val, key) => formatValue(maps[key].get(epoch))) ); console.log(renderTitle(title)); console.table(epochGrid); diff --git a/packages/beacon-node/test/utils/runEl.ts b/packages/beacon-node/test/utils/runEl.ts index 9adf1a83c151..9a407f91d414 100644 --- a/packages/beacon-node/test/utils/runEl.ts +++ b/packages/beacon-node/test/utils/runEl.ts @@ -7,7 +7,6 @@ import {Eth1Provider} from "../../src/index.js"; import {ZERO_HASH} from "../../src/constants/index.js"; import {shell} from "../sim/shell.js"; -/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable no-console */ let txRpcId = 1; @@ -84,7 +83,7 @@ async function waitForELOnline(url: string, signal: AbortSignal): Promise console.log("Waiting for few seconds for EL to fully setup, for e.g. unlock the account..."); await sleep(5000, signal); return; // Done - } catch (e) { + } catch (_e) { await sleep(1000, signal); } } @@ -180,8 +179,8 @@ async function startELProcess(args: { return tearDownCallBack; } -async function waitForELOffline(ENGINE_PORT: string): Promise { - const port = parseInt(ENGINE_PORT); +async function waitForELOffline(enginePort: string): Promise { + const port = parseInt(enginePort); for (let i = 0; i < 60; i++) { console.log("Waiting for EL offline..."); @@ -197,7 +196,7 @@ async function waitForELOffline(ENGINE_PORT: string): Promise { async function isPortInUse(port: number): Promise { return new Promise((resolve, reject) => { const server = net.createServer(); - server.once("error", function (err) { + server.once("error", (err) => { if ((err as unknown as {code: string}).code === "EADDRINUSE") { resolve(true); } else { @@ -205,7 +204,7 @@ async function isPortInUse(port: number): Promise { } }); - server.once("listening", function () { + server.once("listening", () => { // close the server if listening doesn't fail server.close(() => { resolve(false); diff --git a/packages/beacon-node/test/utils/state.ts b/packages/beacon-node/test/utils/state.ts index bb55adca1eb0..6ad85f3422f7 100644 --- a/packages/beacon-node/test/utils/state.ts +++ b/packages/beacon-node/test/utils/state.ts @@ -1,10 +1,10 @@ import {SecretKey} from "@chainsafe/blst"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {config as minimalConfig} from "@lodestar/config/default"; import { BeaconStateAllForks, CachedBeaconStateAllForks, createCachedBeaconState, - PubkeyIndexMap, CachedBeaconStateBellatrix, BeaconStateBellatrix, CachedBeaconStateElectra, @@ -106,6 +106,7 @@ export function generateState( /** * This generates state with default pubkey + * TODO: (@matthewkeil) - this is duplicated and exists in state-transition as well */ export function generateCachedState(opts?: TestBeaconState): CachedBeaconStateAllForks { const config = getConfig(ForkName.phase0); diff --git a/packages/beacon-node/test/utils/validationData/aggregateAndProof.ts b/packages/beacon-node/test/utils/validationData/aggregateAndProof.ts index 45adfec433e4..462502719134 100644 --- a/packages/beacon-node/test/utils/validationData/aggregateAndProof.ts +++ b/packages/beacon-node/test/utils/validationData/aggregateAndProof.ts @@ -1,7 +1,6 @@ import {computeSigningRoot} from "@lodestar/state-transition"; import {DOMAIN_AGGREGATE_AND_PROOF, DOMAIN_SELECTION_PROOF} from "@lodestar/params"; import {phase0, ssz} from "@lodestar/types"; -// eslint-disable-next-line import/no-relative-packages import {getSecretKeyFromIndexCached} from "../../../../state-transition/test/perf/util.js"; import {IBeaconChain} from "../../../src/chain/index.js"; import {SeenAggregators} from "../../../src/chain/seenCache/index.js"; diff --git a/packages/beacon-node/test/utils/validationData/attestation.ts b/packages/beacon-node/test/utils/validationData/attestation.ts index c33d942dabc5..6715512ac667 100644 --- a/packages/beacon-node/test/utils/validationData/attestation.ts +++ b/packages/beacon-node/test/utils/validationData/attestation.ts @@ -1,17 +1,11 @@ import {BitArray, toHexString} from "@chainsafe/ssz"; -import { - computeEpochAtSlot, - computeSigningRoot, - computeStartSlotAtEpoch, - getShufflingDecisionBlock, -} from "@lodestar/state-transition"; +import {computeEpochAtSlot, computeSigningRoot, computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {ProtoBlock, IForkChoice, ExecutionStatus, DataAvailabilityStatus} from "@lodestar/fork-choice"; import {DOMAIN_BEACON_ATTESTER} from "@lodestar/params"; import {phase0, Slot, ssz} from "@lodestar/types"; import { generateTestCachedBeaconStateOnlyValidators, getSecretKeyFromIndexCached, - // eslint-disable-next-line import/no-relative-packages } from "../../../../state-transition/test/perf/util.js"; import {IBeaconChain} from "../../../src/chain/index.js"; import {IStateRegenerator} from "../../../src/chain/regen/index.js"; @@ -81,10 +75,20 @@ export function getAttestationValidData(opts: AttestationValidDataOpts): { dataAvailabilityStatus: DataAvailabilityStatus.PreData, }; - const shufflingCache = new ShufflingCache(); - shufflingCache.processState(state, state.epochCtx.currentShuffling.epoch); - shufflingCache.processState(state, state.epochCtx.nextShuffling.epoch); - const dependentRoot = getShufflingDecisionBlock(state, state.epochCtx.currentShuffling.epoch); + const shufflingCache = new ShufflingCache(null, null, {}, [ + { + shuffling: state.epochCtx.previousShuffling, + decisionRoot: state.epochCtx.previousDecisionRoot, + }, + { + shuffling: state.epochCtx.currentShuffling, + decisionRoot: state.epochCtx.currentDecisionRoot, + }, + { + shuffling: state.epochCtx.nextShuffling, + decisionRoot: state.epochCtx.nextDecisionRoot, + }, + ]); const forkChoice = { getBlock: (root) => { @@ -95,7 +99,7 @@ export function getAttestationValidData(opts: AttestationValidDataOpts): { if (rootHex !== toHexString(beaconBlockRoot)) return null; return headBlock; }, - getDependentRoot: () => dependentRoot, + getDependentRoot: () => state.epochCtx.currentDecisionRoot, } as Partial as IForkChoice; const committeeIndices = state.epochCtx.getBeaconCommittee(attSlot, attIndex); diff --git a/packages/cli/docsgen/markdown.ts b/packages/cli/docsgen/markdown.ts index 5c3033e9c0df..9c7a4d0ca279 100644 --- a/packages/cli/docsgen/markdown.ts +++ b/packages/cli/docsgen/markdown.ts @@ -117,7 +117,6 @@ function renderOption(optionName: string, option: CliOptionDefinition): string | defaultValue = `"${defaultValue}"`; } if (option.type === "array") { - // eslint-disable-next-line quotes if (!defaultValue.includes(`"`)) { defaultValue = `"${defaultValue}"`; } diff --git a/packages/cli/package.json b/packages/cli/package.json index 579725eba04e..6f44a6c2926e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@chainsafe/lodestar", - "version": "1.22.0", + "version": "1.23.0", "description": "Command line interface for lodestar", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -28,8 +28,8 @@ "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\" lodestar --help", "check-types": "tsc", "docs:build": "node --loader ts-node/esm ./docsgen/index.ts", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test:unit": "vitest --run --dir test/unit/", "test:e2e": "vitest --run --config vitest.e2e.config.ts --dir test/e2e/", "test:sim:multifork": "LODESTAR_PRESET=minimal DOTENV_CONFIG_PATH=../../.env.test node -r dotenv/config --loader ts-node/esm test/sim/multiFork.test.ts", @@ -53,26 +53,26 @@ "dependencies": { "@chainsafe/bls-keygen": "^0.4.0", "@chainsafe/bls-keystore": "^3.1.0", - "@chainsafe/blst": "^2.0.3", + "@chainsafe/blst": "^2.2.0", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", "@chainsafe/persistent-merkle-tree": "^0.8.0", - "@chainsafe/ssz": "^0.17.1", + "@chainsafe/ssz": "^0.18.0", "@chainsafe/threads": "^1.11.1", "@libp2p/crypto": "^4.1.0", "@libp2p/peer-id": "^4.1.0", "@libp2p/peer-id-factory": "^4.1.0", - "@lodestar/api": "^1.22.0", - "@lodestar/beacon-node": "^1.22.0", - "@lodestar/config": "^1.22.0", - "@lodestar/db": "^1.22.0", - "@lodestar/light-client": "^1.22.0", - "@lodestar/logger": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/state-transition": "^1.22.0", - "@lodestar/types": "^1.22.0", - "@lodestar/utils": "^1.22.0", - "@lodestar/validator": "^1.22.0", + "@lodestar/api": "^1.23.0", + "@lodestar/beacon-node": "^1.23.0", + "@lodestar/config": "^1.23.0", + "@lodestar/db": "^1.23.0", + "@lodestar/light-client": "^1.23.0", + "@lodestar/logger": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/state-transition": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0", + "@lodestar/validator": "^1.23.0", "@multiformats/multiaddr": "^12.1.3", "deepmerge": "^4.3.1", "ethers": "^6.7.0", @@ -88,12 +88,12 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.22.0", + "@lodestar/test-utils": "^1.23.0", "@types/debug": "^4.1.7", "@types/got": "^9.6.12", "@types/inquirer": "^9.0.3", "@types/proper-lockfile": "^4.1.4", "@types/yargs": "^17.0.24", - "fastify": "^4.27.0" + "fastify": "^5.0.0" } } diff --git a/packages/cli/src/applyPreset.ts b/packages/cli/src/applyPreset.ts index 25f78b7d32ac..612c5d648c63 100644 --- a/packages/cli/src/applyPreset.ts +++ b/packages/cli/src/applyPreset.ts @@ -1,8 +1,6 @@ // MUST import this file first before anything and not import any Lodestar code. -// eslint-disable-next-line no-restricted-imports import {hasher} from "@chainsafe/persistent-merkle-tree/lib/hasher/as-sha256.js"; -// eslint-disable-next-line no-restricted-imports import {setHasher} from "@chainsafe/persistent-merkle-tree/lib/hasher/index.js"; // without setting this first, persistent-merkle-tree will use noble instead @@ -45,7 +43,6 @@ else if (network) { if (network === "dev") { process.env.LODESTAR_PRESET = "minimal"; // "c-kzg" has hardcoded the mainnet value, do not use presets - // eslint-disable-next-line @typescript-eslint/naming-convention setActivePreset(PresetName.minimal, {FIELD_ELEMENTS_PER_BLOB: 4096}); } else if (network === "gnosis" || network === "chiado") { process.env.LODESTAR_PRESET = "gnosis"; @@ -57,7 +54,6 @@ else if (process.argv[2] === "dev") { process.env.LODESTAR_PRESET = "minimal"; process.env.LODESTAR_NETWORK = "dev"; // "c-kzg" has hardcoded the mainnet value, do not use presets - // eslint-disable-next-line @typescript-eslint/naming-convention setActivePreset(PresetName.minimal, {FIELD_ELEMENTS_PER_BLOB: 4096}); } @@ -94,6 +90,3 @@ function valueOfArg(argName: string): string | null { return null; } - -// Add empty export to make this a module -export {}; diff --git a/packages/cli/src/cmds/beacon/handler.ts b/packages/cli/src/cmds/beacon/handler.ts index d51af66e37d0..ea424e04824a 100644 --- a/packages/cli/src/cmds/beacon/handler.ts +++ b/packages/cli/src/cmds/beacon/handler.ts @@ -158,7 +158,6 @@ export async function beaconHandler(args: BeaconArgs & GlobalArgs): Promise = { // Each bootnode entry could be comma separated, just deserialize it into a single array // as comma separated entries are generally most friendly in ansible kind of setups, i.e. // [ "en1", "en2,en3" ] => [ 'en1', 'en2', 'en3' ] - coerce: (args: string[]) => args.map((item) => item.split(",")).flat(1), + coerce: (args: string[]) => args.flatMap((item) => item.split(",")), group: "network", }, diff --git a/packages/cli/src/cmds/dev/options.ts b/packages/cli/src/cmds/dev/options.ts index c484150e58d7..e442b0605cdc 100644 --- a/packages/cli/src/cmds/dev/options.ts +++ b/packages/cli/src/cmds/dev/options.ts @@ -81,15 +81,19 @@ const externalOptionsOverrides: Partial closeMetrics()); // only start server if metrics are explicitly enabled - if (args["metrics"]) { + if (args.metrics) { const port = args["metrics.port"] ?? validatorMetricsDefaultOptions.port; const address = args["metrics.address"] ?? validatorMetricsDefaultOptions.address; const metricsServer = await getHttpMetricsServer({port, address}, {register, logger}); @@ -157,6 +157,7 @@ export async function validatorHandler(args: IValidatorCliArgs & GlobalArgs): Pr globalInit: { requestWireFormat: parseWireFormat(args, "http.requestWireFormat"), responseWireFormat: parseWireFormat(args, "http.responseWireFormat"), + headers: {"User-Agent": `Lodestar/${version}`}, }, }, logger, @@ -185,7 +186,7 @@ export async function validatorHandler(args: IValidatorCliArgs & GlobalArgs): Pr // Start keymanager API backend // Only if keymanagerEnabled flag is set to true - if (args["keymanager"]) { + if (args.keymanager) { // if proposerSettingsFile provided disable the key proposerConfigWrite in keymanager const proposerConfigWriteDisabled = args.proposerSettingsFile !== undefined; if (proposerConfigWriteDisabled) { @@ -208,6 +209,7 @@ export async function validatorHandler(args: IValidatorCliArgs & GlobalArgs): Pr isAuthEnabled: args["keymanager.auth"], headerLimit: args["keymanager.headerLimit"], bodyLimit: args["keymanager.bodyLimit"], + stacktraces: args["keymanager.stacktraces"], tokenFile: args["keymanager.tokenFile"], tokenDir: dbPath, }, @@ -232,7 +234,7 @@ function getProposerConfigFromArgs( builder: { gasLimit: args.defaultGasLimit, selection: parseBuilderSelection( - args["builder.selection"] ?? (args["builder"] ? defaultOptions.builderAliasSelection : undefined) + args["builder.selection"] ?? (args.builder ? defaultOptions.builderAliasSelection : undefined) ), boostFactor: parseBuilderBoostFactor(args["builder.boostFactor"]), }, diff --git a/packages/cli/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts b/packages/cli/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts index 0ce0646c978a..7cfaa9768494 100644 --- a/packages/cli/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts +++ b/packages/cli/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts @@ -51,7 +51,7 @@ export async function decryptKeystoreDefinitions( opts.logger.debug("Loaded keystores via keystore cache"); return signers; - } catch { + } catch (_e) { // Some error loading the cache, ignore and invalidate cache await clearKeystoreCache(opts.cacheFilePath); } diff --git a/packages/cli/src/cmds/validator/keymanager/decryptKeystores/poolSize.ts b/packages/cli/src/cmds/validator/keymanager/decryptKeystores/poolSize.ts index 9219ae6d8537..2939dade7e31 100644 --- a/packages/cli/src/cmds/validator/keymanager/decryptKeystores/poolSize.ts +++ b/packages/cli/src/cmds/validator/keymanager/decryptKeystores/poolSize.ts @@ -6,7 +6,7 @@ try { } else { maxPoolSize = (await import("node:os")).availableParallelism(); } -} catch (e) { +} catch (_e) { maxPoolSize = 8; } diff --git a/packages/cli/src/cmds/validator/keymanager/impl.ts b/packages/cli/src/cmds/validator/keymanager/impl.ts index 2915c125eac2..b4233f3162cd 100644 --- a/packages/cli/src/cmds/validator/keymanager/impl.ts +++ b/packages/cli/src/cmds/validator/keymanager/impl.ts @@ -1,5 +1,4 @@ import {Keystore} from "@chainsafe/bls-keystore"; -import {fromHexString} from "@chainsafe/ssz"; import {SecretKey} from "@chainsafe/blst"; import { DeleteRemoteKeyStatus, @@ -21,7 +20,7 @@ import {KeymanagerApiMethods as Api} from "@lodestar/api/keymanager/server"; import {Interchange, SignerType, Validator} from "@lodestar/validator"; import {ApiError} from "@lodestar/api/server"; import {Epoch} from "@lodestar/types"; -import {isValidHttpUrl} from "@lodestar/utils"; +import {fromHex, isValidHttpUrl} from "@lodestar/utils"; import {getPubkeyHexFromKeystore, isValidatePubkeyHex} from "../../../util/format.js"; import {parseFeeRecipient} from "../../../util/index.js"; import {DecryptKeystoresThreadPool} from "./decryptKeystores/index.js"; @@ -224,7 +223,7 @@ export class KeymanagerApi implements Api { } } - const pubkeysBytes = pubkeys.map((pubkeyHex) => fromHexString(pubkeyHex)); + const pubkeysBytes = pubkeys.map((pubkeyHex) => fromHex(pubkeyHex)); const interchangeV5 = await this.validator.exportInterchange(pubkeysBytes, { version: "5", @@ -379,7 +378,6 @@ export class KeymanagerApi implements Api { function ensureJSON(strOrJson: string | T): T { if (typeof strOrJson === "string") { return JSON.parse(strOrJson) as T; - } else { - return strOrJson; } + return strOrJson; } diff --git a/packages/cli/src/cmds/validator/keymanager/server.ts b/packages/cli/src/cmds/validator/keymanager/server.ts index 03880c8b8842..e48c708c96a8 100644 --- a/packages/cli/src/cmds/validator/keymanager/server.ts +++ b/packages/cli/src/cmds/validator/keymanager/server.ts @@ -1,10 +1,10 @@ import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; -import {toHexString} from "@chainsafe/ssz"; import {RestApiServer, RestApiServerOpts, RestApiServerModules} from "@lodestar/beacon-node"; import {KeymanagerApiMethods, registerRoutes} from "@lodestar/api/keymanager/server"; import {ChainForkConfig} from "@lodestar/config"; +import {toHex} from "@lodestar/utils"; import {writeFile600Perm} from "../../../util/index.js"; export type KeymanagerRestApiServerOpts = RestApiServerOpts & { @@ -21,6 +21,7 @@ export const keymanagerRestApiServerOptsDefault: KeymanagerRestApiServerOpts = { isAuthEnabled: true, // Slashing protection DB has been reported to be 3MB https://github.com/ChainSafe/lodestar/issues/4530 bodyLimit: 20 * 1024 * 1024, // 20MB + stacktraces: false, }; export type KeymanagerRestApiServerModules = RestApiServerModules & { @@ -50,7 +51,7 @@ export class KeymanagerRestApiServer extends RestApiServer { if (opts.isAuthEnabled) { // Generate a new token if token file does not exist or file do exist, but is empty - bearerToken = readFileIfExists(apiTokenPath) ?? `api-token-${toHexString(crypto.randomBytes(32))}`; + bearerToken = readFileIfExists(apiTokenPath) ?? `api-token-${toHex(crypto.randomBytes(32))}`; writeFile600Perm(apiTokenPath, bearerToken, {encoding: "utf8"}); } @@ -79,6 +80,7 @@ function readFileIfExists(filepath: string): string | null { return fs.readFileSync(filepath, "utf8").trim(); } catch (e) { if ((e as {code: string}).code === "ENOENT") return null; - else throw e; + + throw e; } } diff --git a/packages/cli/src/cmds/validator/options.ts b/packages/cli/src/cmds/validator/options.ts index aaa0e96d25a7..3beb8197793c 100644 --- a/packages/cli/src/cmds/validator/options.ts +++ b/packages/cli/src/cmds/validator/options.ts @@ -91,6 +91,7 @@ export type KeymanagerArgs = { "keymanager.cors"?: string; "keymanager.headerLimit"?: number; "keymanager.bodyLimit"?: number; + "keymanager.stacktraces"?: boolean; }; export const keymanagerOptions: CliCommandOptions = { @@ -141,6 +142,11 @@ export const keymanagerOptions: CliCommandOptions = { type: "number", description: "Defines the maximum payload, in bytes, the server is allowed to accept", }, + "keymanager.stacktraces": { + hidden: true, + type: "boolean", + description: "Return stacktraces in HTTP error responses", + }, }; export const validatorOptions: CliCommandOptions = { @@ -189,7 +195,7 @@ export const validatorOptions: CliCommandOptions = { string: true, coerce: (urls: string[]): string[] => // Parse ["url1,url2"] to ["url1", "url2"] - urls.map((item) => item.split(",")).flat(1), + urls.flatMap((item) => item.split(",")), alias: ["server"], // for backwards compatibility }, @@ -346,8 +352,7 @@ export const validatorOptions: CliCommandOptions = { coerce: (pubkeys: string[]): string[] => // Parse ["0x11,0x22"] to ["0x11", "0x22"] pubkeys - .map((item) => item.split(",")) - .flat(1) + .flatMap((item) => item.split(",")) .map(ensure0xPrefix), group: "externalSigner", }, diff --git a/packages/cli/src/cmds/validator/signers/importExternalKeystores.ts b/packages/cli/src/cmds/validator/signers/importExternalKeystores.ts index 7b90d16d1d88..f86ea90ab1a8 100644 --- a/packages/cli/src/cmds/validator/signers/importExternalKeystores.ts +++ b/packages/cli/src/cmds/validator/signers/importExternalKeystores.ts @@ -28,17 +28,17 @@ export function importKeystoreDefinitionsFromExternalDir(args: { export async function readPassphraseOrPrompt(args: {importKeystoresPassword?: string}): Promise { if (args.importKeystoresPassword) { return readPassphraseFile(args.importKeystoresPassword); - } else { - const answers = await inquirer.prompt<{password: string}>([ - { - name: "password", - type: "password", - message: "Enter the keystore(s) password", - }, - ]); - - return answers.password; } + + const answers = await inquirer.prompt<{password: string}>([ + { + name: "password", + type: "password", + message: "Enter the keystore(s) password", + }, + ]); + + return answers.password; } /** diff --git a/packages/cli/src/cmds/validator/signers/index.ts b/packages/cli/src/cmds/validator/signers/index.ts index c155b52d186a..950be2db1cf5 100644 --- a/packages/cli/src/cmds/validator/signers/index.ts +++ b/packages/cli/src/cmds/validator/signers/index.ts @@ -53,13 +53,12 @@ export async function getSignersFromArgs( // Using a remote signer with TESTNETS if (args["externalSigner.pubkeys"] || args["externalSigner.fetch"]) { return getRemoteSigners(args); - } else { - return indexes.map((index) => ({type: SignerType.Local, secretKey: interopSecretKey(index)})); } + return indexes.map((index) => ({type: SignerType.Local, secretKey: interopSecretKey(index)})); } // UNSAFE, ONLY USE FOR TESTNETS - Derive keys directly from a mnemonic - else if (args.fromMnemonic) { + if (args.fromMnemonic) { if (network === defaultNetwork) { throw new YargsError("fromMnemonic must only be used in testnets"); } @@ -76,7 +75,7 @@ export async function getSignersFromArgs( } // Import JSON keystores and run - else if (args.importKeystores) { + if (args.importKeystores) { const keystoreDefinitions = importKeystoreDefinitionsFromExternalDir({ keystoresPath: args.importKeystores, password: await readPassphraseOrPrompt(args), @@ -98,52 +97,50 @@ export async function getSignersFromArgs( ignoreLockFile: args.force, onDecrypt: needle, cacheFilePath: path.join(accountPaths.cacheDir, "imported_keystores.cache"), - disableThreadPool: args["disableKeystoresThreadPool"], + disableThreadPool: args.disableKeystoresThreadPool, logger, signal, }); } // Remote keys are declared manually or will be fetched from external signer - else if (args["externalSigner.pubkeys"] || args["externalSigner.fetch"]) { + if (args["externalSigner.pubkeys"] || args["externalSigner.fetch"]) { return getRemoteSigners(args); } // Read keys from local account manager - else { - const persistedKeysBackend = new PersistedKeysBackend(accountPaths); - - // Read and decrypt local keystores, imported via keymanager api or import cmd - const keystoreDefinitions = persistedKeysBackend.readAllKeystores(); - - const needle = showProgress({ - total: keystoreDefinitions.length, - frequencyMs: KEYSTORE_IMPORT_PROGRESS_MS, - signal, - progress: ({ratePerSec, percentage, current, total}) => { - logger.info( - `${percentage.toFixed(0)}% of local keystores imported. current=${current} total=${total} rate=${( - ratePerSec * 60 - ).toFixed(2)}keys/m` - ); - }, - }); - - const keystoreSigners = await decryptKeystoreDefinitions(keystoreDefinitions, { - ignoreLockFile: args.force, - onDecrypt: needle, - cacheFilePath: path.join(accountPaths.cacheDir, "local_keystores.cache"), - disableThreadPool: args["disableKeystoresThreadPool"], - logger, - signal, - }); - - // Read local remote keys, imported via keymanager api - const signerDefinitions = persistedKeysBackend.readAllRemoteKeys(); - const remoteSigners = signerDefinitions.map(({url, pubkey}): Signer => ({type: SignerType.Remote, url, pubkey})); - - return [...keystoreSigners, ...remoteSigners]; - } + const persistedKeysBackend = new PersistedKeysBackend(accountPaths); + + // Read and decrypt local keystores, imported via keymanager api or import cmd + const keystoreDefinitions = persistedKeysBackend.readAllKeystores(); + + const needle = showProgress({ + total: keystoreDefinitions.length, + frequencyMs: KEYSTORE_IMPORT_PROGRESS_MS, + signal, + progress: ({ratePerSec, percentage, current, total}) => { + logger.info( + `${percentage.toFixed(0)}% of local keystores imported. current=${current} total=${total} rate=${( + ratePerSec * 60 + ).toFixed(2)}keys/m` + ); + }, + }); + + const keystoreSigners = await decryptKeystoreDefinitions(keystoreDefinitions, { + ignoreLockFile: args.force, + onDecrypt: needle, + cacheFilePath: path.join(accountPaths.cacheDir, "local_keystores.cache"), + disableThreadPool: args.disableKeystoresThreadPool, + logger, + signal, + }); + + // Read local remote keys, imported via keymanager api + const signerDefinitions = persistedKeysBackend.readAllRemoteKeys(); + const remoteSigners = signerDefinitions.map(({url, pubkey}): Signer => ({type: SignerType.Remote, url, pubkey})); + + return [...keystoreSigners, ...remoteSigners]; } export function getSignerPubkeyHex(signer: Signer): string { diff --git a/packages/cli/src/cmds/validator/signers/logSigners.ts b/packages/cli/src/cmds/validator/signers/logSigners.ts index 20aa50d25ff6..d245ee75883a 100644 --- a/packages/cli/src/cmds/validator/signers/logSigners.ts +++ b/packages/cli/src/cmds/validator/signers/logSigners.ts @@ -60,11 +60,11 @@ function groupRemoteSignersByUrl(remoteSigners: SignerRemote[]): {url: string; p * is connected with fetching enabled, but otherwise exit the process and suggest a different configuration. */ export function warnOrExitNoSigners(args: IValidatorCliArgs, logger: Pick): void { - if (args["keymanager"] && !args["externalSigner.fetch"]) { + if (args.keymanager && !args["externalSigner.fetch"]) { logger.warn("No local keystores or remote keys found with current args, expecting to be added via keymanager"); - } else if (!args["keymanager"] && args["externalSigner.fetch"]) { + } else if (!args.keymanager && args["externalSigner.fetch"]) { logger.warn("No remote keys found with current args, expecting to be added to external signer and fetched later"); - } else if (args["keymanager"] && args["externalSigner.fetch"]) { + } else if (args.keymanager && args["externalSigner.fetch"]) { logger.warn( "No local keystores or remote keys found with current args, expecting to be added via keymanager or fetched from external signer later" ); diff --git a/packages/cli/src/cmds/validator/slashingProtection/export.ts b/packages/cli/src/cmds/validator/slashingProtection/export.ts index c18b020f5782..f57f9d3ec0bd 100644 --- a/packages/cli/src/cmds/validator/slashingProtection/export.ts +++ b/packages/cli/src/cmds/validator/slashingProtection/export.ts @@ -43,8 +43,7 @@ export const exportCmd: CliCommand // Parse ["0x11,0x22"] to ["0x11", "0x22"] pubkeys - .map((item) => item.split(",")) - .flat(1) + .flatMap((item) => item.split(",")) .map(ensure0xPrefix), }, }, diff --git a/packages/cli/src/cmds/validator/voluntaryExit.ts b/packages/cli/src/cmds/validator/voluntaryExit.ts index a399a763662d..5b4cfdf270f0 100644 --- a/packages/cli/src/cmds/validator/voluntaryExit.ts +++ b/packages/cli/src/cmds/validator/voluntaryExit.ts @@ -59,8 +59,7 @@ If no `pubkeys` are provided, it will exit all validators that have been importe coerce: (pubkeys: string[]): string[] => // Parse ["0x11,0x22"] to ["0x11", "0x22"] pubkeys - .map((item) => item.split(",")) - .flat(1) + .flatMap((item) => item.split(",")) .map(ensure0xPrefix), }, @@ -151,7 +150,7 @@ async function processVoluntaryExit( const voluntaryExit: phase0.VoluntaryExit = {epoch: exitEpoch, validatorIndex: index}; const signingRoot = computeSigningRoot(ssz.phase0.VoluntaryExit, voluntaryExit, domain); - let signature; + let signature: Signature; switch (signer.type) { case SignerType.Local: signature = signer.secretKey.sign(signingRoot); @@ -192,22 +191,19 @@ function selectSignersToExit(args: VoluntaryExitArgs, signers: Signer[]): Signer const signer = signersByPubkey.get(pubkey); if (!signer) { throw new YargsError(`Unknown pubkey ${pubkey}`); - } else { - selectedSigners.push({pubkey, signer}); } + selectedSigners.push({pubkey, signer}); } return selectedSigners; - } else { - return signersWithPubkey; } + return signersWithPubkey; } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function resolveValidatorIndexes(client: ApiClient, signersToExit: SignerPubkey[]) { const pubkeys = signersToExit.map(({pubkey}) => pubkey); - const validators = (await client.beacon.getStateValidators({stateId: "head", validatorIds: pubkeys})).value(); + const validators = (await client.beacon.postStateValidators({stateId: "head", validatorIds: pubkeys})).value(); const dataByPubkey = new Map(validators.map((item) => [toPubkeyHex(item.validator.pubkey), item])); diff --git a/packages/cli/src/config/peerId.ts b/packages/cli/src/config/peerId.ts index d4906c716ba7..576a99e6980e 100644 --- a/packages/cli/src/config/peerId.ts +++ b/packages/cli/src/config/peerId.ts @@ -14,7 +14,8 @@ async function createFromParts(multihash: Uint8Array, privKey?: Uint8Array, pubK const key = await unmarshalPrivateKey(privKey); return createFromPrivKey(key); - } else if (pubKey != null) { + } + if (pubKey != null) { const key = unmarshalPublicKey(pubKey); return createFromPubKey(key); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 5cdccbacfeec..0d509af17ab0 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -14,7 +14,6 @@ void lodestar // Show command help message when no command is provided if (msg.includes("Not enough non-option arguments")) { yarg.showHelp(); - // eslint-disable-next-line no-console console.log("\n"); } } @@ -22,7 +21,6 @@ void lodestar const errorMessage = err !== undefined ? (err instanceof YargsError ? err.message : err.stack) : msg || "Unknown error"; - // eslint-disable-next-line no-console console.error(` ✖ ${errorMessage}\n`); process.exit(1); }) diff --git a/packages/cli/src/networks/dev.ts b/packages/cli/src/networks/dev.ts index ff8afc127dcc..6ce87273b3cb 100644 --- a/packages/cli/src/networks/dev.ts +++ b/packages/cli/src/networks/dev.ts @@ -1,8 +1,9 @@ import {gnosisChainConfig} from "@lodestar/config/networks"; import {minimalChainConfig, mainnetChainConfig} from "@lodestar/config/configs"; import {ACTIVE_PRESET, PresetName} from "@lodestar/params"; +import {ChainConfig} from "@lodestar/config"; -let chainConfig; +let chainConfig: ChainConfig; switch (ACTIVE_PRESET) { case PresetName.mainnet: chainConfig = mainnetChainConfig; diff --git a/packages/cli/src/networks/holesky.ts b/packages/cli/src/networks/holesky.ts index b86f6b543582..63bc6e07f8f2 100644 --- a/packages/cli/src/networks/holesky.ts +++ b/packages/cli/src/networks/holesky.ts @@ -10,7 +10,7 @@ export const bootEnrs = [ "enr:-Ku4QPG7F72mbKx3gEQEx07wpYYusGDh-ni6SNkLvOS-hhN-BxIggN7tKlmalb0L5JPoAfqD-akTZ-gX06hFeBEz4WoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAhnTT-AQFwAP__________gmlkgnY0gmlwhJK-DYCJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyk", "enr:-LK4QPxe-mDiSOtEB_Y82ozvxn9aQM07Ui8A-vQHNgYGMMthfsfOabaaTHhhJHFCBQQVRjBww_A5bM1rf8MlkJU_l68Eh2F0dG5ldHOIAADAAAAAAACEZXRoMpBpt9l0BAFwAAABAAAAAAAAgmlkgnY0gmlwhLKAiOmJc2VjcDI1NmsxoQJu6T9pclPObAzEVQ53DpVQqjadmVxdTLL-J3h9NFoCeIN0Y3CCIyiDdWRwgiMo", "enr:-Ly4QGbOw4xNel5EhmDsJJ-QhC9XycWtsetnWoZ0uRy381GHdHsNHJiCwDTOkb3S1Ade0SFQkWJX_pgb3g8Jfh93rvMBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpBpt9l0BAFwAAABAAAAAAAAgmlkgnY0gmlwhJK-DYCJc2VjcDI1NmsxoQOxKv9sv3zKF8GDewgFGGHKP5HCZZpPpTrwl9eXKAWGxIhzeW5jbmV0cwCDdGNwgiMog3VkcIIjKA", - "enr:-LS4QG0uV4qvcpJ-HFDJRGBmnlD3TJo7yc4jwK8iP7iKaTlfQ5kZvIDspLMJhk7j9KapuL9yyHaZmwTEZqr10k9XumyCEcmHYXR0bmV0c4gAAAAABgAAAIRldGgykGm32XQEAXAAAAEAAAAAAACCaWSCdjSCaXCErK4j-YlzZWNwMjU2azGhAgfWRBEJlb7gAhXIB5ePmjj2b8io0UpEenq1Kl9cxStJg3RjcIIjKIN1ZHCCIyg", + "enr:-KO4QCi3ZY4TM5KL7bAG6laSYiYelDWu0crvUjCXlyc_cwEfUpMIuARuMJYGxWe-UYYpHEw_aBbZ1u-4tHQ8imyI5uaCAsGEZXRoMpBprg6ZBQFwAP__________gmlkgnY0gmlwhKyuI_mJc2VjcDI1NmsxoQLoFG5-vuNX6N49vnkTBaA3ZsBDF8B30DGqWOGtRGz5w4N0Y3CCIyiDdWRwgiMo", "enr:-Le4QLoE1wFHSlGcm48a9ZESb_MRLqPPu6G0vHqu4MaUcQNDHS69tsy-zkN0K6pglyzX8m24mkb-LtBcbjAYdP1uxm4BhGV0aDKQabfZdAQBcAAAAQAAAAAAAIJpZIJ2NIJpcIQ5gR6Wg2lwNpAgAUHQBwEQAAAAAAAAADR-iXNlY3AyNTZrMaEDPMSNdcL92uNIyCsS177Z6KTXlbZakQqxv3aQcWawNXeDdWRwgiMohHVkcDaCI4I", "enr:-KG4QC9Wm32mtzB5Fbj2ri2TEKglHmIWgvwTQCvNHBopuwpNAi1X6qOsBg_Z1-Bee-kfSrhzUQZSgDUyfH5outUprtoBgmlkgnY0gmlwhHEel3eDaXA2kP6AAAAAAAAAAlBW__4Srr-Jc2VjcDI1NmsxoQO7KE63Z4eSI55S1Yn7q9_xFkJ1Wt-a3LgiXuKGs19s0YN1ZHCCIyiEdWRwNoIjKA", ]; diff --git a/packages/cli/src/networks/index.ts b/packages/cli/src/networks/index.ts index 0831b78cd2f6..e2fc9dd621b6 100644 --- a/packages/cli/src/networks/index.ts +++ b/packages/cli/src/networks/index.ts @@ -109,7 +109,6 @@ export async function getNetworkBootnodes(network: NetworkName): Promise = { // Enable all if (namespaces.includes(enabledAll)) return allNamespaces; // Parse ["debug,lodestar"] to ["debug", "lodestar"] - return namespaces.map((val) => val.split(",")).flat(1); + return namespaces.flatMap((val) => val.split(",")); }, }, @@ -92,6 +94,13 @@ export const options: CliCommandOptions = { description: "Defines the maximum payload, in bytes, the server is allowed to accept", }, + "rest.stacktraces": { + hidden: true, + type: "boolean", + description: "Return stacktraces in HTTP error responses", + group: "api", + }, + "rest.swaggerUI": { type: "boolean", description: "Enable Swagger UI for API exploration at http://{address}:{port}/documentation", diff --git a/packages/cli/src/options/beaconNodeOptions/builder.ts b/packages/cli/src/options/beaconNodeOptions/builder.ts index 2c89cbad89d2..d0e4089dd81c 100644 --- a/packages/cli/src/options/beaconNodeOptions/builder.ts +++ b/packages/cli/src/options/beaconNodeOptions/builder.ts @@ -18,7 +18,7 @@ export function parseArgs(args: ExecutionBuilderArgs): IBeaconNodeOptions["execu } return { - enabled: args["builder"], + enabled: args.builder, url: args["builder.url"] ?? defaultExecutionBuilderHttpOpts.url, timeout: args["builder.timeout"], faultInspectionWindow: args["builder.faultInspectionWindow"], diff --git a/packages/cli/src/options/beaconNodeOptions/chain.ts b/packages/cli/src/options/beaconNodeOptions/chain.ts index aae97b6db68f..c5f907804a83 100644 --- a/packages/cli/src/options/beaconNodeOptions/chain.ts +++ b/packages/cli/src/options/beaconNodeOptions/chain.ts @@ -1,5 +1,5 @@ import * as path from "node:path"; -import {defaultOptions, IBeaconNodeOptions} from "@lodestar/beacon-node"; +import {StateArchiveMode, defaultOptions, IBeaconNodeOptions, DEFAULT_STATE_ARCHIVE_MODE} from "@lodestar/beacon-node"; import {CliCommandOptions} from "@lodestar/utils"; export type ChainArgs = { @@ -22,12 +22,13 @@ export type ChainArgs = { "chain.maxSkipSlots"?: number; "chain.trustedSetup"?: string; "safe-slots-to-import-optimistically": number; - "chain.archiveStateEpochFrequency": number; emitPayloadAttributes?: boolean; broadcastValidationStrictness?: string; "chain.minSameMessageSignatureSetsToBatch"?: number; "chain.maxShufflingCacheEpochs"?: number; + "chain.archiveStateEpochFrequency": number; "chain.archiveBlobEpochs"?: number; + "chain.stateArchiveMode": StateArchiveMode; "chain.nHistoricalStates"?: boolean; "chain.nHistoricalStatesFileDataStore"?: boolean; "chain.maxBlockStates"?: number; @@ -36,13 +37,13 @@ export type ChainArgs = { export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] { return { - suggestedFeeRecipient: args["suggestedFeeRecipient"], + suggestedFeeRecipient: args.suggestedFeeRecipient, blsVerifyAllMultiThread: args["chain.blsVerifyAllMultiThread"], blsVerifyAllMainThread: args["chain.blsVerifyAllMainThread"], disableBlsBatchVerify: args["chain.disableBlsBatchVerify"], persistProducedBlocks: args["chain.persistProducedBlocks"], persistInvalidSszObjects: args["chain.persistInvalidSszObjects"], - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: persistInvalidSszObjectsDir: undefined as any, proposerBoost: args["chain.proposerBoost"], proposerBoostReorg: args["chain.proposerBoostReorg"], @@ -54,13 +55,14 @@ export function parseArgs(args: ChainArgs): IBeaconNodeOptions["chain"] { maxSkipSlots: args["chain.maxSkipSlots"], trustedSetup: args["chain.trustedSetup"], safeSlotsToImportOptimistically: args["safe-slots-to-import-optimistically"], - archiveStateEpochFrequency: args["chain.archiveStateEpochFrequency"], - emitPayloadAttributes: args["emitPayloadAttributes"], - broadcastValidationStrictness: args["broadcastValidationStrictness"], + emitPayloadAttributes: args.emitPayloadAttributes, + broadcastValidationStrictness: args.broadcastValidationStrictness, minSameMessageSignatureSetsToBatch: args["chain.minSameMessageSignatureSetsToBatch"] ?? defaultOptions.chain.minSameMessageSignatureSetsToBatch, maxShufflingCacheEpochs: args["chain.maxShufflingCacheEpochs"] ?? defaultOptions.chain.maxShufflingCacheEpochs, + archiveStateEpochFrequency: args["chain.archiveStateEpochFrequency"], archiveBlobEpochs: args["chain.archiveBlobEpochs"], + stateArchiveMode: args["chain.stateArchiveMode"] ?? defaultOptions.chain.stateArchiveMode, nHistoricalStates: args["chain.nHistoricalStates"] ?? defaultOptions.chain.nHistoricalStates, nHistoricalStatesFileDataStore: args["chain.nHistoricalStatesFileDataStore"] ?? defaultOptions.chain.nHistoricalStatesFileDataStore, @@ -210,6 +212,15 @@ Will double processing times. Use only for debugging purposes.", group: "chain", }, + "chain.stateArchiveMode": { + hidden: true, + choices: Object.values(StateArchiveMode), + description: `Strategy to manage archive states, only support ${DEFAULT_STATE_ARCHIVE_MODE} at this time`, + default: defaultOptions.chain.stateArchiveMode, + type: "string", + group: "chain", + }, + broadcastValidationStrictness: { // TODO: hide the option till validations fully implemented hidden: true, diff --git a/packages/cli/src/options/beaconNodeOptions/eth1.ts b/packages/cli/src/options/beaconNodeOptions/eth1.ts index 46654cca6b2e..f12a1704dec7 100644 --- a/packages/cli/src/options/beaconNodeOptions/eth1.ts +++ b/packages/cli/src/options/beaconNodeOptions/eth1.ts @@ -25,14 +25,12 @@ export function parseArgs(args: Eth1Args & Partial): IBeaco // jwt auth mechanism. if (providerUrls === undefined && args["execution.urls"]) { providerUrls = args["execution.urls"]; - jwtSecretHex = args["jwtSecret"] - ? extractJwtHexSecret(fs.readFileSync(args["jwtSecret"], "utf-8").trim()) - : undefined; - jwtId = args["jwtId"]; + jwtSecretHex = args.jwtSecret ? extractJwtHexSecret(fs.readFileSync(args.jwtSecret, "utf-8").trim()) : undefined; + jwtId = args.jwtId; } return { - enabled: args["eth1"], + enabled: args.eth1, providerUrls, jwtSecretHex, jwtId, @@ -59,7 +57,7 @@ export const options: CliCommandOptions = { string: true, coerce: (urls: string[]): string[] => // Parse ["url1,url2"] to ["url1", "url2"] - urls.map((item) => item.split(",")).flat(1), + urls.flatMap((item) => item.split(",")), group: "eth1", }, diff --git a/packages/cli/src/options/beaconNodeOptions/execution.ts b/packages/cli/src/options/beaconNodeOptions/execution.ts index 85b81a5e708b..2d32ee2ae786 100644 --- a/packages/cli/src/options/beaconNodeOptions/execution.ts +++ b/packages/cli/src/options/beaconNodeOptions/execution.ts @@ -30,10 +30,8 @@ export function parseArgs(args: ExecutionEngineArgs): IBeaconNodeOptions["execut * jwtSecret is parsed as hex instead of bytes because the merge with defaults * in beaconOptions messes up the bytes array as as index => value object */ - jwtSecretHex: args["jwtSecret"] - ? extractJwtHexSecret(fs.readFileSync(args["jwtSecret"], "utf-8").trim()) - : undefined, - jwtId: args["jwtId"], + jwtSecretHex: args.jwtSecret ? extractJwtHexSecret(fs.readFileSync(args.jwtSecret, "utf-8").trim()) : undefined, + jwtId: args.jwtId, }; } @@ -45,7 +43,7 @@ export const options: CliCommandOptions = { string: true, coerce: (urls: string[]): string[] => // Parse ["url1,url2"] to ["url1", "url2"] - urls.map((item) => item.split(",")).flat(1), + urls.flatMap((item) => item.split(",")), group: "execution", }, diff --git a/packages/cli/src/options/beaconNodeOptions/metrics.ts b/packages/cli/src/options/beaconNodeOptions/metrics.ts index ba12a7546eae..ded63b7dc24d 100644 --- a/packages/cli/src/options/beaconNodeOptions/metrics.ts +++ b/packages/cli/src/options/beaconNodeOptions/metrics.ts @@ -9,7 +9,7 @@ export type MetricsArgs = { export function parseArgs(args: MetricsArgs): IBeaconNodeOptions["metrics"] { return { - enabled: args["metrics"], + enabled: args.metrics, port: args["metrics.port"], address: args["metrics.address"], }; diff --git a/packages/cli/src/options/beaconNodeOptions/network.ts b/packages/cli/src/options/beaconNodeOptions/network.ts index 25ba036a5dbf..cfd109bdc50a 100644 --- a/packages/cli/src/options/beaconNodeOptions/network.ts +++ b/packages/cli/src/options/beaconNodeOptions/network.ts @@ -26,7 +26,6 @@ export type NetworkArgs = { "network.connectToDiscv5Bootnodes"?: boolean; "network.discv5FirstQueryDelayMs"?: number; "network.dontSendGossipAttestationsToForkchoice"?: boolean; - "network.beaconAttestationBatchValidation"?: boolean; "network.allowPublishToZeroPeers"?: boolean; "network.gossipsubD"?: number; "network.gossipsubDLow"?: number; @@ -58,18 +57,17 @@ function validateMultiaddrArg>(args } } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function parseListenArgs(args: NetworkArgs) { // If listenAddress is explicitly set, use it // If listenAddress6 is not set, use defaultListenAddress const listenAddress = args.listenAddress ?? (args.listenAddress6 ? undefined : defaultListenAddress); - const port = listenAddress ? args.port ?? defaultP2pPort : undefined; - const discoveryPort = listenAddress ? args.discoveryPort ?? args.port ?? defaultP2pPort : undefined; + const port = listenAddress ? (args.port ?? defaultP2pPort) : undefined; + const discoveryPort = listenAddress ? (args.discoveryPort ?? args.port ?? defaultP2pPort) : undefined; // Only use listenAddress6 if it is explicitly set const listenAddress6 = args.listenAddress6; - const port6 = listenAddress6 ? args.port6 ?? defaultP2pPort6 : undefined; - const discoveryPort6 = listenAddress6 ? args.discoveryPort6 ?? args.port6 ?? defaultP2pPort6 : undefined; + const port6 = listenAddress6 ? (args.port6 ?? defaultP2pPort6) : undefined; + const discoveryPort6 = listenAddress6 ? (args.discoveryPort6 ?? args.port6 ?? defaultP2pPort6) : undefined; return {listenAddress, port, discoveryPort, listenAddress6, port6, discoveryPort6}; } @@ -102,21 +100,21 @@ export function parseArgs(args: NetworkArgs): IBeaconNodeOptions["network"] { const bindMu6 = listenAddress6 ? `${muArgs.listenAddress6}${muArgs.discoveryPort6}` : undefined; const localMu6 = listenAddress6 ? `${muArgs.listenAddress6}${muArgs.port6}` : undefined; - const targetPeers = args["targetPeers"]; + const targetPeers = args.targetPeers; const maxPeers = args["network.maxPeers"] ?? (targetPeers !== undefined ? Math.floor(targetPeers * 1.1) : undefined); if (targetPeers != null && maxPeers != null && targetPeers > maxPeers) { throw new YargsError("network.maxPeers must be greater than or equal to targetPeers"); } // Set discv5 opts to null to disable only if explicitly disabled - const enableDiscv5 = args["discv5"] ?? true; + const enableDiscv5 = args.discv5 ?? true; // TODO: Okay to set to empty array? - const bootEnrs = args["bootnodes"] ?? []; + const bootEnrs = args.bootnodes ?? []; // throw if user-provided enrs are invalid for (const enrStr of bootEnrs) { try { ENR.decodeTxt(enrStr); - } catch (e) { + } catch (_e) { throw new YargsError(`Provided ENR in bootnodes is invalid:\n ${enrStr}`); } } @@ -130,28 +128,27 @@ export function parseArgs(args: NetworkArgs): IBeaconNodeOptions["network"] { ip6: bindMu6, }, bootEnrs, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: enr: undefined as any, } : null, maxPeers: maxPeers ?? defaultOptions.network.maxPeers, targetPeers: targetPeers ?? defaultOptions.network.targetPeers, localMultiaddrs: [localMu, localMu6].filter(Boolean) as string[], - subscribeAllSubnets: args["subscribeAllSubnets"], + subscribeAllSubnets: args.subscribeAllSubnets, slotsToSubscribeBeforeAggregatorDuty: - args["slotsToSubscribeBeforeAggregatorDuty"] ?? defaultOptions.network.slotsToSubscribeBeforeAggregatorDuty, - disablePeerScoring: args["disablePeerScoring"], + args.slotsToSubscribeBeforeAggregatorDuty ?? defaultOptions.network.slotsToSubscribeBeforeAggregatorDuty, + disablePeerScoring: args.disablePeerScoring, connectToDiscv5Bootnodes: args["network.connectToDiscv5Bootnodes"], discv5FirstQueryDelayMs: args["network.discv5FirstQueryDelayMs"], dontSendGossipAttestationsToForkchoice: args["network.dontSendGossipAttestationsToForkchoice"], - beaconAttestationBatchValidation: args["network.beaconAttestationBatchValidation"], allowPublishToZeroPeers: args["network.allowPublishToZeroPeers"], gossipsubD: args["network.gossipsubD"], gossipsubDLow: args["network.gossipsubDLow"], gossipsubDHigh: args["network.gossipsubDHigh"], gossipsubAwaitHandler: args["network.gossipsubAwaitHandler"], disableFloodPublish: args["network.disableFloodPublish"], - mdns: args["mdns"], + mdns: args.mdns, rateLimitMultiplier: args["network.rateLimitMultiplier"], maxGossipTopicConcurrency: args["network.maxGossipTopicConcurrency"], useWorker: args["network.useWorker"], @@ -214,12 +211,12 @@ export const options: CliCommandOptions = { bootnodes: { type: "array", description: "Bootnodes for discv5 discovery", - defaultDescription: JSON.stringify((defaultOptions.network.discv5 || {}).bootEnrs || []), + defaultDescription: JSON.stringify(defaultOptions.network.discv5?.bootEnrs || []), group: "network", // Each bootnode entry could be comma separated, just deserialize it into a single array // as comma separated entries are generally most friendly in ansible kind of setups, i.e. // [ "en1", "en2,en3" ] => [ 'en1', 'en2', 'en3' ] - coerce: (args: string[]) => args.map((item) => item.split(",")).flat(1), + coerce: (args: string[]) => args.flatMap((item) => item.split(",")), }, targetPeers: { @@ -321,13 +318,6 @@ export const options: CliCommandOptions = { group: "network", }, - "network.beaconAttestationBatchValidation": { - hidden: true, - type: "boolean", - description: "Validate gossip attestations in batches", - group: "network", - }, - "network.allowPublishToZeroPeers": { hidden: true, type: "boolean", diff --git a/packages/cli/src/options/logOptions.ts b/packages/cli/src/options/logOptions.ts index b45057a4532f..09eb5b1d56cb 100644 --- a/packages/cli/src/options/logOptions.ts +++ b/packages/cli/src/options/logOptions.ts @@ -65,6 +65,6 @@ export const logOptions: CliCommandOptions = { description: "Set log level for a specific module by name: 'chain=debug' or 'network=debug,chain=debug'", type: "array", string: true, - coerce: (args: string[]) => args.map((item) => item.split(",")).flat(1), + coerce: (args: string[]) => args.flatMap((item) => item.split(",")), }, }; diff --git a/packages/cli/src/options/paramsOptions.ts b/packages/cli/src/options/paramsOptions.ts index b35a15e3c9b6..bd89ffa77d9e 100644 --- a/packages/cli/src/options/paramsOptions.ts +++ b/packages/cli/src/options/paramsOptions.ts @@ -25,14 +25,14 @@ export function parseBeaconParamsArgs(args: Record): IB } const paramsOptionsByName = ObjectKeys(chainConfigTypes).reduce( - (options: Record, key): Record => ({ - ...options, - [getArgKey(key)]: { + (options: Record, key): Record => { + options[getArgKey(key)] = { hidden: true, type: "string", group: "params", - }, - }), + }; + return options; + }, {} ); diff --git a/packages/cli/src/util/file.ts b/packages/cli/src/util/file.ts index 99e93a96dc59..a18768d456b9 100644 --- a/packages/cli/src/util/file.ts +++ b/packages/cli/src/util/file.ts @@ -13,7 +13,6 @@ export const yamlSchema = FAILSAFE_SCHEMA.extend({ new Type("tag:yaml.org,2002:str", { kind: "scalar", construct: function construct(data) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return data !== null ? data : ""; }, }), @@ -105,9 +104,8 @@ export function readFileIfExists(filepath: string, acceptedFormats?: string[] } catch (e) { if ((e as {code: string}).code === "ENOENT") { return null; - } else { - throw e; } + throw e; } } @@ -142,9 +140,8 @@ export async function downloadOrLoadFile(pathOrUrl: string): Promise if (isUrl(pathOrUrl)) { const res = await got.get(pathOrUrl, {encoding: "binary"}); return res.rawBody; - } else { - return fs.promises.readFile(pathOrUrl); } + return fs.promises.readFile(pathOrUrl); } /** diff --git a/packages/cli/src/util/format.ts b/packages/cli/src/util/format.ts index 01c2753193a4..e8361649824a 100644 --- a/packages/cli/src/util/format.ts +++ b/packages/cli/src/util/format.ts @@ -1,5 +1,5 @@ import {PublicKey} from "@chainsafe/blst"; -import {fromHexString} from "@chainsafe/ssz"; +import {fromHex} from "@lodestar/utils"; /** * 0x prefix a string if not prefixed already @@ -36,8 +36,8 @@ export function parseRange(range: string): number[] { const [from, to] = range.split("..").map((n) => parseInt(n)); - if (isNaN(from)) throw Error(`Invalid range from isNaN '${range}'`); - if (isNaN(to)) throw Error(`Invalid range to isNaN '${range}'`); + if (Number.isNaN(from)) throw Error(`Invalid range from isNaN '${range}'`); + if (Number.isNaN(to)) throw Error(`Invalid range to isNaN '${range}'`); if (from > to) throw Error(`Invalid range from > to '${range}'`); const arr: number[] = []; @@ -50,7 +50,7 @@ export function parseRange(range: string): number[] { export function assertValidPubkeysHex(pubkeysHex: string[]): void { for (const pubkeyHex of pubkeysHex) { - const pubkeyBytes = fromHexString(pubkeyHex); + const pubkeyBytes = fromHex(pubkeyHex); PublicKey.fromBytes(pubkeyBytes, true); } } diff --git a/packages/cli/src/util/fs.ts b/packages/cli/src/util/fs.ts index 37464c877b85..17501bea78c9 100644 --- a/packages/cli/src/util/fs.ts +++ b/packages/cli/src/util/fs.ts @@ -21,7 +21,8 @@ export function unlinkSyncMaybe(filepath: string): boolean { } catch (e) { const {code} = e as ErrorFs; if (code === "ENOENT") return false; - else throw e; + + throw e; } } @@ -37,7 +38,8 @@ export function rmdirSyncMaybe(dirpath: string): boolean { // about error codes https://nodejs.org/api/fs.html#fspromisesrmdirpath-options // ENOENT error on Windows and an ENOTDIR if (code === "ENOENT" || code === "ENOTDIR") return false; - else throw e; + + throw e; } } diff --git a/packages/cli/src/util/gitData/gitDataPath.ts b/packages/cli/src/util/gitData/gitDataPath.ts index e243ca433f2d..1ad3104aafc6 100644 --- a/packages/cli/src/util/gitData/gitDataPath.ts +++ b/packages/cli/src/util/gitData/gitDataPath.ts @@ -4,7 +4,6 @@ import {fileURLToPath} from "node:url"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Persist git data and distribute through NPM so CLI consumers can know exactly diff --git a/packages/cli/src/util/gitData/index.ts b/packages/cli/src/util/gitData/index.ts index 96c5e4bbaef2..0720d39d9e30 100644 --- a/packages/cli/src/util/gitData/index.ts +++ b/packages/cli/src/util/gitData/index.ts @@ -11,7 +11,7 @@ export function readAndGetGitData(): GitData { let persistedGitData: Partial; try { persistedGitData = readGitDataFile(); - } catch (e) { + } catch (_e) { persistedGitData = {}; } @@ -23,13 +23,13 @@ export function readAndGetGitData(): GitData { branch: currentGitData.branch && currentGitData.branch.length > 0 ? currentGitData.branch - : persistedGitData.branch ?? "", + : (persistedGitData.branch ?? ""), commit: currentGitData.commit && currentGitData.commit.length > 0 ? currentGitData.commit - : persistedGitData.commit ?? "", + : (persistedGitData.commit ?? ""), }; - } catch (e) { + } catch (_e) { return { branch: "", commit: "", @@ -49,7 +49,7 @@ export function getGitData(): GitData { function getBranch(): string { try { return shellSilent("git rev-parse --abbrev-ref HEAD"); - } catch (e) { + } catch (_e) { return ""; } } @@ -58,7 +58,7 @@ function getBranch(): string { function getCommit(): string { try { return shellSilent("git rev-parse --verify HEAD"); - } catch (e) { + } catch (_e) { return ""; } } diff --git a/packages/cli/src/util/jwt.ts b/packages/cli/src/util/jwt.ts index e77e9e692cd6..b79b0a0dea7f 100644 --- a/packages/cli/src/util/jwt.ts +++ b/packages/cli/src/util/jwt.ts @@ -2,7 +2,7 @@ export function extractJwtHexSecret(jwtSecretContents: string): string { const hexPattern = new RegExp(/^(0x|0X)?(?[a-fA-F0-9]+)$/, "g"); const jwtSecretHexMatch = hexPattern.exec(jwtSecretContents); const jwtSecret = jwtSecretHexMatch?.groups?.jwtSecret; - if (!jwtSecret || jwtSecret.length != 64) { + if (!jwtSecret || jwtSecret.length !== 64) { throw Error(`Need a valid 256 bit hex encoded secret ${jwtSecret} ${jwtSecretContents}`); } // Return the secret in proper hex format diff --git a/packages/cli/src/util/logger.ts b/packages/cli/src/util/logger.ts index e08029f5f1df..7a394c9ce25f 100644 --- a/packages/cli/src/util/logger.ts +++ b/packages/cli/src/util/logger.ts @@ -89,14 +89,16 @@ export function cleanOldLogFiles(args: LogArgs, paths: {defaultLogFilepath: stri .filter((logFileName) => shouldDeleteLogFile(prefix, extension, logFileName, args.logFileDailyRotate)) .map((logFileName) => path.join(folder, logFileName)); // delete files - toDelete.forEach((filename) => fs.unlinkSync(filename)); + for (const filename of toDelete) { + fs.unlinkSync(filename); + } } export function shouldDeleteLogFile(prefix: string, extension: string, logFileName: string, maxFiles: number): boolean { const maxDifferenceMs = maxFiles * 24 * 60 * 60 * 1000; const match = logFileName.match(new RegExp(`${prefix}-([0-9]{4}-[0-9]{2}-[0-9]{2}).${extension}`)); // if match[1] exists, it should be the date pattern of YYYY-MM-DD - if (match && match[1] && Date.now() - new Date(match[1]).getTime() > maxDifferenceMs) { + if (match?.[1] && Date.now() - new Date(match[1]).getTime() > maxDifferenceMs) { return true; } return false; diff --git a/packages/cli/src/util/object.ts b/packages/cli/src/util/object.ts index d37f41ea6655..7f2e8a49d6c8 100644 --- a/packages/cli/src/util/object.ts +++ b/packages/cli/src/util/object.ts @@ -3,10 +3,10 @@ import {RecursivePartial} from "@lodestar/utils"; /** * Removes (mutates) all properties with a value === undefined, recursively */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any + +// biome-ignore lint/suspicious/noExplicitAny: export function removeUndefinedRecursive(obj: T): RecursivePartial { for (const key of Object.keys(obj)) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const value = obj[key]; if (value && typeof value === "object") removeUndefinedRecursive(value); else if (value === undefined) delete obj[key]; diff --git a/packages/cli/src/util/process.ts b/packages/cli/src/util/process.ts index cd040bdffb55..e274d782b8ba 100644 --- a/packages/cli/src/util/process.ts +++ b/packages/cli/src/util/process.ts @@ -8,7 +8,6 @@ const exitSignals = ["SIGTERM", "SIGINT"] as NodeJS.Signals[]; */ export function onGracefulShutdown( cleanUpFunction: () => Promise, - // eslint-disable-next-line no-console logFn: (msg: string) => void = console.log ): void { for (const signal of exitSignals) { diff --git a/packages/cli/src/util/proposerConfig.ts b/packages/cli/src/util/proposerConfig.ts index 509dc8213df1..2c12ce8f8524 100644 --- a/packages/cli/src/util/proposerConfig.ts +++ b/packages/cli/src/util/proposerConfig.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - import fs from "node:fs"; import path from "node:path"; import {ValidatorProposerConfig} from "@lodestar/validator"; diff --git a/packages/cli/src/util/types.ts b/packages/cli/src/util/types.ts index ced3bf34b1f6..bf194170f3e2 100644 --- a/packages/cli/src/util/types.ts +++ b/packages/cli/src/util/types.ts @@ -1,7 +1,8 @@ /** * Typed `Object.keys(o: T)` function, returning `(keyof T)[]` */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/naming-convention + +// biome-ignore lint/suspicious/noExplicitAny: export function ObjectKeys(o: T): (keyof T)[] { return Object.keys(o); } diff --git a/packages/cli/src/util/version.ts b/packages/cli/src/util/version.ts index 4752856e5b1d..624412107603 100644 --- a/packages/cli/src/util/version.ts +++ b/packages/cli/src/util/version.ts @@ -6,7 +6,6 @@ import {readAndGetGitData} from "./gitData/index.js"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); type VersionJson = { diff --git a/packages/cli/test/e2e/blsToExecutionchange.test.ts b/packages/cli/test/e2e/blsToExecutionchange.test.ts index 51720e424c7e..57f32421d313 100644 --- a/packages/cli/test/e2e/blsToExecutionchange.test.ts +++ b/packages/cli/test/e2e/blsToExecutionchange.test.ts @@ -8,7 +8,7 @@ import {interopSecretKey} from "@lodestar/state-transition"; import {execCliCommand, spawnCliCommand, stopChildProcess} from "@lodestar/test-utils"; import {testFilesDir} from "../utils.js"; -describe("bLSToExecutionChange cmd", function () { +describe("bLSToExecutionChange cmd", () => { vi.setConfig({testTimeout: 60_000}); it("Perform bLSToExecutionChange", async () => { diff --git a/packages/cli/test/e2e/importFromFsDirect.test.ts b/packages/cli/test/e2e/importFromFsDirect.test.ts index 644635bc0ffe..6f612e84afe6 100644 --- a/packages/cli/test/e2e/importFromFsDirect.test.ts +++ b/packages/cli/test/e2e/importFromFsDirect.test.ts @@ -7,24 +7,17 @@ import {testFilesDir} from "../utils.js"; import {cachedPubkeysHex, cachedSeckeysHex} from "../utils/cachedKeys.js"; import {expectKeys, startValidatorWithKeyManager} from "../utils/validator.js"; -describe("import from fs same cmd as validate", function () { +describe("import from fs same cmd as validate", () => { vi.setConfig({testTimeout: 30_000}); const dataDir = path.join(testFilesDir, "import-and-validate-test"); const importFromDir = path.join(dataDir, "eth2.0_deposit_out"); const passphraseFilepath = path.join(importFromDir, "password.text"); - beforeAll(() => { + beforeAll(async () => { rimraf.sync(dataDir); rimraf.sync(importFromDir); - }); - - const passphrase = "AAAAAAAA0000000000"; - const keyCount = 2; - const pubkeys = cachedPubkeysHex.slice(0, keyCount); - const secretKeys = cachedSeckeysHex.slice(0, keyCount); - beforeAll(async () => { // Produce and encrypt keystores const keystoresStr = await getKeystoresStr(passphrase, secretKeys); @@ -35,6 +28,11 @@ describe("import from fs same cmd as validate", function () { } }); + const passphrase = "AAAAAAAA0000000000"; + const keyCount = 2; + const pubkeys = cachedPubkeysHex.slice(0, keyCount); + const secretKeys = cachedSeckeysHex.slice(0, keyCount); + // Check that there are not keys loaded without adding extra args `--importKeystores` it("run 'validator' there are no keys loaded", async () => { const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], { diff --git a/packages/cli/test/e2e/importFromFsPreStep.test.ts b/packages/cli/test/e2e/importFromFsPreStep.test.ts index 7eebf2d4946e..437180ef07e5 100644 --- a/packages/cli/test/e2e/importFromFsPreStep.test.ts +++ b/packages/cli/test/e2e/importFromFsPreStep.test.ts @@ -8,7 +8,7 @@ import {testFilesDir} from "../utils.js"; import {cachedPubkeysHex, cachedSeckeysHex} from "../utils/cachedKeys.js"; import {expectKeys, startValidatorWithKeyManager} from "../utils/validator.js"; -describe("import from fs then validate", function () { +describe("import from fs then validate", () => { vi.setConfig({testTimeout: 30_000}); const dataDir = path.join(testFilesDir, "import-then-validate-test"); @@ -47,7 +47,7 @@ describe("import from fs then validate", function () { } }); - it("run 'validator list' and check pubkeys are imported", async function () { + it("run 'validator list' and check pubkeys are imported", async () => { fs.mkdirSync(path.join(dataDir, "keystores"), {recursive: true}); fs.mkdirSync(path.join(dataDir, "secrets"), {recursive: true}); @@ -58,7 +58,7 @@ describe("import from fs then validate", function () { } }); - it("run 'validator' check keys are loaded", async function () { + it("run 'validator' check keys are loaded", async () => { const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); onTestFinished(async () => { await stopValidator(); diff --git a/packages/cli/test/e2e/importKeystoresFromApi.test.ts b/packages/cli/test/e2e/importKeystoresFromApi.test.ts index b7abe1e8d293..7f71e2977a92 100644 --- a/packages/cli/test/e2e/importKeystoresFromApi.test.ts +++ b/packages/cli/test/e2e/importKeystoresFromApi.test.ts @@ -12,7 +12,7 @@ import {cachedPubkeysHex, cachedSeckeysHex} from "../utils/cachedKeys.js"; import {expectDeepEquals} from "../utils/runUtils.js"; import {expectKeys, startValidatorWithKeyManager} from "../utils/validator.js"; -describe("import keystores from api", function () { +describe("import keystores from api", () => { vi.setConfig({testTimeout: 30_000}); const dataDir = path.join(testFilesDir, "import-keystores-test"); @@ -30,7 +30,6 @@ describe("import keystores from api", function () { const genesisValidatorsRoot = "0x0000000000000000000000000000000000000000000000000000000000000000"; const slashingProtection: Interchange = { - /* eslint-disable @typescript-eslint/naming-convention */ metadata: { interchange_format_version: "5", genesis_validators_root: genesisValidatorsRoot, @@ -119,7 +118,7 @@ describe("import keystores from api", function () { }); }); - it("run 'validator' check keys are loaded + delete", async function () { + it("run 'validator' check keys are loaded + delete", async () => { const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); onTestFinished(async () => { await stopValidator(); @@ -139,7 +138,7 @@ describe("import keystores from api", function () { await expectKeys(keymanagerClient, [], "Wrong listKeys after deleting"); }); - it("different process check no keys are loaded", async function () { + it("different process check no keys are loaded", async () => { const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); onTestFinished(async () => { await stopValidator(); @@ -149,7 +148,7 @@ describe("import keystores from api", function () { await expectKeys(keymanagerClient, [], "Wrong listKeys"); }); - it("reject calls without bearerToken", async function () { + it("reject calls without bearerToken", async () => { const {stopValidator} = await startValidatorWithKeyManager([], {dataDir}); onTestFinished(async () => { await stopValidator(); diff --git a/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts b/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts index 0d7e4aa58da3..b66611cbf4f4 100644 --- a/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts +++ b/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts @@ -20,7 +20,7 @@ async function expectKeys(keymanagerClient: ApiClient, expectedPubkeys: string[] ); } -describe("import remoteKeys from api", function () { +describe("import remoteKeys from api", () => { vi.setConfig({testTimeout: 30_000}); const dataDir = path.join(testFilesDir, "import-remoteKeys-test"); @@ -65,7 +65,7 @@ describe("import remoteKeys from api", function () { ); }); - it("run 'validator' check keys are loaded + delete", async function () { + it("run 'validator' check keys are loaded + delete", async () => { const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); onTestFinished(async () => { await stopValidator(); @@ -86,7 +86,7 @@ describe("import remoteKeys from api", function () { await expectKeys(keymanagerClient, [], "Wrong listRemoteKeys after deleting"); }); - it("reject calls without bearerToken", async function () { + it("reject calls without bearerToken", async () => { const {stopValidator} = await startValidatorWithKeyManager([], {dataDir}); onTestFinished(async () => { await stopValidator(); diff --git a/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts b/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts index ffeb40460f40..2f25c82999a7 100644 --- a/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts +++ b/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts @@ -9,7 +9,7 @@ import {cachedPubkeysHex, cachedSeckeysHex} from "../utils/cachedKeys.js"; import {expectDeepEquals} from "../utils/runUtils.js"; import {startValidatorWithKeyManager} from "../utils/validator.js"; -describe("import keystores from api, test DefaultProposerConfig", function () { +describe("import keystores from api, test DefaultProposerConfig", () => { vi.setConfig({testTimeout: 30_000}); const dataDir = path.join(testFilesDir, "proposer-config-test"); @@ -39,7 +39,6 @@ describe("import keystores from api, test DefaultProposerConfig", function () { const genesisValidatorsRoot = "0x0000000000000000000000000000000000000000000000000000000000000000"; const slashingProtection: Interchange = { - /* eslint-disable @typescript-eslint/naming-convention */ metadata: { interchange_format_version: "5", genesis_validators_root: genesisValidatorsRoot, diff --git a/packages/cli/test/e2e/runDevCmd.test.ts b/packages/cli/test/e2e/runDevCmd.test.ts index 68dcca156d88..3beb68393815 100644 --- a/packages/cli/test/e2e/runDevCmd.test.ts +++ b/packages/cli/test/e2e/runDevCmd.test.ts @@ -4,7 +4,7 @@ import {config} from "@lodestar/config/default"; import {retry} from "@lodestar/utils"; import {spawnCliCommand, stopChildProcess} from "@lodestar/test-utils"; -describe("Run dev command", function () { +describe("Run dev command", () => { vi.setConfig({testTimeout: 30_000}); it("Run dev command with no --dataDir until beacon api is listening", async () => { diff --git a/packages/cli/test/e2e/validatorList.test.ts b/packages/cli/test/e2e/validatorList.test.ts index b6fe4da5faeb..7f86fb8ef89b 100644 --- a/packages/cli/test/e2e/validatorList.test.ts +++ b/packages/cli/test/e2e/validatorList.test.ts @@ -9,7 +9,7 @@ import {runCliCommand} from "@lodestar/test-utils"; import {testFilesDir} from "../utils.js"; import {getLodestarCli} from "../../src/cli.js"; -describe("cmds / validator", function () { +describe("cmds / validator", () => { vi.setConfig({testTimeout: 30_000}); const lodestar = getLodestarCli(); @@ -54,7 +54,7 @@ describe("cmds / validator", function () { expect(console.log).toHaveBeenCalledWith(`Imported keystore ${pkHex} ${keystoreFilepath}`); }); - it("should list validators", async function () { + it("should list validators", async () => { fs.mkdirSync(path.join(dataDir, "keystores"), {recursive: true}); fs.mkdirSync(path.join(dataDir, "secrets"), {recursive: true}); diff --git a/packages/cli/test/e2e/voluntaryExit.test.ts b/packages/cli/test/e2e/voluntaryExit.test.ts index 49499e891731..0abddcab7652 100644 --- a/packages/cli/test/e2e/voluntaryExit.test.ts +++ b/packages/cli/test/e2e/voluntaryExit.test.ts @@ -7,7 +7,7 @@ import {interopSecretKey} from "@lodestar/state-transition"; import {spawnCliCommand, execCliCommand, stopChildProcess} from "@lodestar/test-utils"; import {testFilesDir} from "../utils.js"; -describe("voluntaryExit cmd", function () { +describe("voluntaryExit cmd", () => { vi.setConfig({testTimeout: 60_000}); it("Perform a voluntary exit", async () => { @@ -78,10 +78,8 @@ describe("voluntaryExit cmd", function () { 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} = ${validator.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 664091d5c520..97ac06d5fc5c 100644 --- a/packages/cli/test/e2e/voluntaryExitFromApi.test.ts +++ b/packages/cli/test/e2e/voluntaryExitFromApi.test.ts @@ -8,7 +8,7 @@ import {spawnCliCommand, stopChildProcess} from "@lodestar/test-utils"; import {retry} from "@lodestar/utils"; import {testFilesDir} from "../utils.js"; -describe("voluntary exit from api", function () { +describe("voluntary exit from api", () => { vi.setConfig({testTimeout: 60_000}); it("Perform a voluntary exit", async () => { @@ -85,10 +85,8 @@ describe("voluntary exit from api", function () { 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} = ${validator.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 a9cf2f48a168..769380f2053f 100644 --- a/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts +++ b/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts @@ -14,7 +14,7 @@ import { } from "@lodestar/test-utils"; import {testFilesDir} from "../utils.js"; -describe("voluntaryExit using remote signer", function () { +describe("voluntaryExit using remote signer", () => { vi.setConfig({testTimeout: 30_000}); let externalSigner: StartedExternalSigner; @@ -100,10 +100,8 @@ describe("voluntaryExit using remote signer", function () { 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} = ${validator.status}`); } + console.log(`Confirmed validator ${pubkey} = ${validator.status}`); }, {retryDelay: 1000, retries: 20} ); diff --git a/packages/cli/test/scripts/e2e_test_env.ts b/packages/cli/test/scripts/e2e_test_env.ts index 24d0142827a8..fcecfac5e1c7 100644 --- a/packages/cli/test/scripts/e2e_test_env.ts +++ b/packages/cli/test/scripts/e2e_test_env.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import path from "node:path"; import {BeaconClient, ExecutionClient} from "../utils/crucible/interfaces.js"; import {Simulation} from "../utils/crucible/simulation.js"; diff --git a/packages/cli/test/sim/backupEthProvider.test.ts b/packages/cli/test/sim/backupEthProvider.test.ts index 1310119c9c3e..4ccc131a58bc 100644 --- a/packages/cli/test/sim/backupEthProvider.test.ts +++ b/packages/cli/test/sim/backupEthProvider.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import path from "node:path"; import {activePreset} from "@lodestar/params"; import {Simulation} from "../utils/crucible/simulation.js"; diff --git a/packages/cli/test/sim/deneb.test.ts b/packages/cli/test/sim/deneb.test.ts index b9c30a5ff523..865540ae7635 100644 --- a/packages/cli/test/sim/deneb.test.ts +++ b/packages/cli/test/sim/deneb.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import path from "node:path"; import {Simulation} from "../utils/crucible/simulation.js"; import {BeaconClient, ExecutionClient, ValidatorClient} from "../utils/crucible/interfaces.js"; diff --git a/packages/cli/test/sim/endpoints.test.ts b/packages/cli/test/sim/endpoints.test.ts index 07cd5fec0cc4..308ac6a0053c 100644 --- a/packages/cli/test/sim/endpoints.test.ts +++ b/packages/cli/test/sim/endpoints.test.ts @@ -1,8 +1,8 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import path from "node:path"; import assert from "node:assert"; import {toHexString} from "@chainsafe/ssz"; -import {routes} from "@lodestar/api"; +import {routes, fetch} from "@lodestar/api"; +import {ssz} from "@lodestar/types"; 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,7 +40,7 @@ await env.start({runTimeoutMs: estimatedTimeoutMs}); const node = env.nodes[0].beacon; await waitForSlot("Wait for 2 slots before checking endpoints", {env, slot: 2}); -const validators = (await node.api.beacon.getStateValidators({stateId: "head"})).value(); +const validators = (await node.api.beacon.postStateValidators({stateId: "head"})).value(); await env.tracker.assert("should have correct validators count called without filters", async () => { assert.equal(validators.length, validatorCount); @@ -55,12 +55,12 @@ await env.tracker.assert("should have correct validator index for second validat }); await env.tracker.assert( - "should return correct number of filtered validators when getStateValidators called with filters", + "should return correct number of filtered validators when postStateValidators called with filters", async () => { const filterPubKey = "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"; - const res = await node.api.beacon.getStateValidators({stateId: "head", validatorIds: [filterPubKey]}); + const res = await node.api.beacon.postStateValidators({stateId: "head", validatorIds: [filterPubKey]}); assert.equal(res.value().length, 1); @@ -71,12 +71,12 @@ await env.tracker.assert( ); await env.tracker.assert( - "should return correct filtered validators when getStateValidators called with filters", + "should return correct filtered validators when postStateValidators called with filters", async () => { const filterPubKey = "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"; - const res = await node.api.beacon.getStateValidators({stateId: "head", validatorIds: [filterPubKey]}); + const res = await node.api.beacon.postStateValidators({stateId: "head", validatorIds: [filterPubKey]}); assert.equal(toHexString(res.value()[0].validator.pubkey), filterPubKey); } @@ -105,6 +105,35 @@ await env.tracker.assert( } ); +await env.tracker.assert("should return HTTP error responses in a spec compliant format", async () => { + // ApiError with status 400 is thrown by handler + const res1 = await node.api.beacon.getStateValidator({stateId: "current", validatorId: 1}); + assert.deepStrictEqual(JSON.parse(await res1.errorBody()), {code: 400, message: "Invalid block id 'current'"}); + + // JSON schema validation failed + const res2 = await node.api.beacon.getPoolAttestationsV2({slot: "current" as unknown as number, committeeIndex: 123}); + assert.deepStrictEqual(JSON.parse(await res2.errorBody()), {code: 400, message: "slot must be integer"}); + + // Error processing multiple items + const signedAttestations = Array.from({length: 3}, () => ssz.phase0.Attestation.defaultValue()); + const res3 = await node.api.beacon.submitPoolAttestationsV2({signedAttestations}); + const errBody = JSON.parse(await res3.errorBody()) as {code: number; message: string; failures: unknown[]}; + assert.equal(errBody.code, 400); + assert.equal(errBody.message, "Error processing attestations"); + assert.equal(errBody.failures.length, signedAttestations.length); + assert.deepStrictEqual(errBody.failures[0], { + index: 0, + message: "ATTESTATION_ERROR_NOT_EXACTLY_ONE_AGGREGATION_BIT_SET", + }); + + // Route does not exist + const res4 = await fetch(`${node.restPublicUrl}/not/implemented/route`); + assert.deepStrictEqual(JSON.parse(await res4.text()), { + code: 404, + message: "Route GET:/not/implemented/route not found", + }); +}); + await env.tracker.assert("BN Not Synced", async () => { const expectedSyncStatus: routes.node.SyncingStatus = { headSlot: 2, diff --git a/packages/cli/test/sim/mixedClient.test.ts b/packages/cli/test/sim/mixedClient.test.ts index bfb2a5a99b1b..45029a1b26b4 100644 --- a/packages/cli/test/sim/mixedClient.test.ts +++ b/packages/cli/test/sim/mixedClient.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import path from "node:path"; import {Simulation} from "../utils/crucible/simulation.js"; import {nodeAssertion} from "../utils/crucible/assertions/nodeAssertion.js"; diff --git a/packages/cli/test/sim/multiFork.test.ts b/packages/cli/test/sim/multiFork.test.ts index 47ad59a165b6..047d9196239d 100644 --- a/packages/cli/test/sim/multiFork.test.ts +++ b/packages/cli/test/sim/multiFork.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import path from "node:path"; import {Match, BeaconClient, ExecutionClient, ValidatorClient} from "../utils/crucible/interfaces.js"; import {Simulation} from "../utils/crucible/simulation.js"; diff --git a/packages/cli/test/unit/cmds/beacon.test.ts b/packages/cli/test/unit/cmds/beacon.test.ts index fc1f50ad1251..6e7b7f389a29 100644 --- a/packages/cli/test/unit/cmds/beacon.test.ts +++ b/packages/cli/test/unit/cmds/beacon.test.ts @@ -186,7 +186,6 @@ describe("initPeerIdAndEnr", () => { }); }); -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type async function runBeaconHandlerInit(args: Partial) { return beaconHandlerInit({ logLevel: LogLevel.info, diff --git a/packages/cli/test/unit/config/beaconParams.test.ts b/packages/cli/test/unit/config/beaconParams.test.ts index a7ce463200d0..2a78d498bf89 100644 --- a/packages/cli/test/unit/config/beaconParams.test.ts +++ b/packages/cli/test/unit/config/beaconParams.test.ts @@ -16,7 +16,6 @@ describe("config / beaconParams", () => { const testCases: { id: string; kwargs: Parameters[0]; - // eslint-disable-next-line @typescript-eslint/naming-convention GENESIS_FORK_VERSION: string; }[] = [ { @@ -24,7 +23,6 @@ describe("config / beaconParams", () => { kwargs: { additionalParamsCli: {}, }, - // eslint-disable-next-line @typescript-eslint/naming-convention GENESIS_FORK_VERSION: GENESIS_FORK_VERSION_MAINNET, }, { @@ -33,7 +31,6 @@ describe("config / beaconParams", () => { network: networkName, additionalParamsCli: {}, }, - // eslint-disable-next-line @typescript-eslint/naming-convention GENESIS_FORK_VERSION: GENESIS_FORK_VERSION_HOLESKY, }, { @@ -43,7 +40,6 @@ describe("config / beaconParams", () => { paramsFile: paramsFilepath, additionalParamsCli: {}, }, - // eslint-disable-next-line @typescript-eslint/naming-convention GENESIS_FORK_VERSION: GENESIS_FORK_VERSION_FILE, }, { @@ -51,16 +47,13 @@ describe("config / beaconParams", () => { kwargs: { network: networkName, paramsFile: paramsFilepath, - // eslint-disable-next-line @typescript-eslint/naming-convention additionalParamsCli: {GENESIS_FORK_VERSION: GENESIS_FORK_VERSION_CLI}, }, - // eslint-disable-next-line @typescript-eslint/naming-convention GENESIS_FORK_VERSION: GENESIS_FORK_VERSION_CLI, }, ]; beforeAll(() => { - // eslint-disable-next-line @typescript-eslint/naming-convention fs.writeFileSync(paramsFilepath, yaml.dump({GENESIS_FORK_VERSION: GENESIS_FORK_VERSION_FILE})); }); @@ -68,7 +61,6 @@ describe("config / beaconParams", () => { if (fs.existsSync(paramsFilepath)) fs.unlinkSync(paramsFilepath); }); - // eslint-disable-next-line @typescript-eslint/naming-convention it.each(testCases)("$id", ({kwargs, GENESIS_FORK_VERSION}) => { const params = getBeaconParams(kwargs); expect(toHexString(params.GENESIS_FORK_VERSION)).toBe(GENESIS_FORK_VERSION); diff --git a/packages/cli/test/unit/db.test.ts b/packages/cli/test/unit/db.test.ts index 1e19e514e9e5..b39c9492c9cb 100644 --- a/packages/cli/test/unit/db.test.ts +++ b/packages/cli/test/unit/db.test.ts @@ -1,7 +1,5 @@ import {describe, it} from "vitest"; -// eslint-disable-next-line import/no-relative-packages import {Bucket as BeaconBucket} from "../../../beacon-node/src/db/buckets.js"; -// eslint-disable-next-line import/no-relative-packages import {Bucket as ValidatorBucket} from "../../../validator/src/buckets.js"; describe("no db bucket overlap", () => { diff --git a/packages/cli/test/unit/options/beaconNodeOptions.test.ts b/packages/cli/test/unit/options/beaconNodeOptions.test.ts index d74ae73b966f..8d295197b541 100644 --- a/packages/cli/test/unit/options/beaconNodeOptions.test.ts +++ b/packages/cli/test/unit/options/beaconNodeOptions.test.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import {describe, it, expect} from "vitest"; -import {IBeaconNodeOptions} from "@lodestar/beacon-node"; +import {StateArchiveMode, IBeaconNodeOptions} from "@lodestar/beacon-node"; import {RecursivePartial} from "@lodestar/utils"; import {parseBeaconNodeArgs, BeaconNodeArgs} from "../../../src/options/beaconNodeOptions/index.js"; import {getTestdirPath} from "../../utils.js"; @@ -17,6 +17,7 @@ describe("options / beaconNodeOptions", () => { "rest.port": 7654, "rest.headerLimit": 16384, "rest.bodyLimit": 30e6, + "rest.stacktraces": true, "chain.blsVerifyAllMultiThread": true, "chain.blsVerifyAllMainThread": true, @@ -42,6 +43,7 @@ describe("options / beaconNodeOptions", () => { "chain.nHistoricalStatesFileDataStore": true, "chain.maxBlockStates": 100, "chain.maxCPStateEpochsInMemory": 100, + "chain.stateArchiveMode": StateArchiveMode.Frequency, emitPayloadAttributes: false, eth1: true, @@ -94,7 +96,6 @@ describe("options / beaconNodeOptions", () => { "network.blockCountPeerLimit": 500, "network.rateTrackerTimeoutMs": 60000, "network.dontSendGossipAttestationsToForkchoice": true, - "network.beaconAttestationBatchValidation": true, "network.allowPublishToZeroPeers": true, "network.gossipsubD": 4, "network.gossipsubDLow": 2, @@ -122,6 +123,7 @@ describe("options / beaconNodeOptions", () => { port: 7654, headerLimit: 16384, bodyLimit: 30e6, + stacktraces: true, }, }, chain: { @@ -146,6 +148,7 @@ describe("options / beaconNodeOptions", () => { minSameMessageSignatureSetsToBatch: 32, maxShufflingCacheEpochs: 100, archiveBlobEpochs: 10000, + stateArchiveMode: StateArchiveMode.Frequency, nHistoricalStates: true, nHistoricalStatesFileDataStore: true, maxBlockStates: 100, @@ -204,7 +207,6 @@ describe("options / beaconNodeOptions", () => { connectToDiscv5Bootnodes: true, discv5FirstQueryDelayMs: 1000, dontSendGossipAttestationsToForkchoice: true, - beaconAttestationBatchValidation: true, allowPublishToZeroPeers: true, gossipsubD: 4, gossipsubDLow: 2, diff --git a/packages/cli/test/unit/options/paramsOptions.test.ts b/packages/cli/test/unit/options/paramsOptions.test.ts index a08c70008612..d6563203e652 100644 --- a/packages/cli/test/unit/options/paramsOptions.test.ts +++ b/packages/cli/test/unit/options/paramsOptions.test.ts @@ -11,9 +11,7 @@ describe("options / paramsOptions", () => { }; const expectedBeaconParams: Partial = { - // eslint-disable-next-line @typescript-eslint/naming-convention GENESIS_FORK_VERSION: "0x00000001", - // eslint-disable-next-line @typescript-eslint/naming-convention DEPOSIT_CONTRACT_ADDRESS: "0x1234567890123456789012345678901234567890", }; diff --git a/packages/cli/test/unit/util/format.test.ts b/packages/cli/test/unit/util/format.test.ts index c06259cc1842..194cc1dcbde7 100644 --- a/packages/cli/test/unit/util/format.test.ts +++ b/packages/cli/test/unit/util/format.test.ts @@ -17,8 +17,7 @@ describe("util / format / parseRange", () => { describe("util / format / isValidatePubkeyHex", () => { const testCases: Record = { - "../../malicious_link/0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95": - false, + "../../malicious_link/0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95": false, "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95": true, "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c9": false, "0x933ad9491b62059dd065b560d256d8957a8c402cc6e8d8ee7290ae11e8f7329267a8811c397529dac52ae1342ba58c95f": false, diff --git a/packages/cli/test/unit/util/gitData.test.ts b/packages/cli/test/unit/util/gitData.test.ts index 206dd070b545..9e15fc2f4956 100644 --- a/packages/cli/test/unit/util/gitData.test.ts +++ b/packages/cli/test/unit/util/gitData.test.ts @@ -8,10 +8,9 @@ import {getGitData} from "../../../src/util/index.js"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); -describe("util / gitData", function () { +describe("util / gitData", () => { // .gitData file is created at build time with the command // ``` // npm run write-git-data diff --git a/packages/cli/test/unit/util/logger.test.ts b/packages/cli/test/unit/util/logger.test.ts index bddc86f2a483..fa17218bfd1c 100644 --- a/packages/cli/test/unit/util/logger.test.ts +++ b/packages/cli/test/unit/util/logger.test.ts @@ -1,7 +1,7 @@ import {describe, it, expect, vi, beforeEach, afterEach} from "vitest"; import {shouldDeleteLogFile} from "../../../src/util/logger.js"; -describe("shouldDeleteLogFile", function () { +describe("shouldDeleteLogFile", () => { const prefix = "beacon"; const extension = "log"; diff --git a/packages/cli/test/unit/validator/keys.test.ts b/packages/cli/test/unit/validator/keys.test.ts index c977c2242c33..6902d17b3883 100644 --- a/packages/cli/test/unit/validator/keys.test.ts +++ b/packages/cli/test/unit/validator/keys.test.ts @@ -10,7 +10,7 @@ describe("validator / signers / importKeystoreDefinitionsFromExternalDir", () => if (tmpDir) fs.rmSync(tmpDir, {recursive: true}); }); - it("should filter out deposit data files", function () { + it("should filter out deposit data files", () => { tmpDir = fs.mkdtempSync("cli-keystores-import-test"); // Populate dir diff --git a/packages/cli/test/unit/validator/parseProposerConfig.test.ts b/packages/cli/test/unit/validator/parseProposerConfig.test.ts index 66220ca3329a..83c8c63ef877 100644 --- a/packages/cli/test/unit/validator/parseProposerConfig.test.ts +++ b/packages/cli/test/unit/validator/parseProposerConfig.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import path from "node:path"; import {fileURLToPath} from "node:url"; import {describe, it, expect} from "vitest"; @@ -27,6 +26,7 @@ const testValue = { builder: { gasLimit: 35000000, selection: routes.validator.BuilderSelection.BuilderAlways, + // biome-ignore lint/correctness/noPrecisionLoss: boostFactor: BigInt(18446744073709551616), }, }, diff --git a/packages/cli/test/utils/crucible/assertions/blobsAssertion.ts b/packages/cli/test/utils/crucible/assertions/blobsAssertion.ts index dece5bc58ce8..50c9ed13f972 100644 --- a/packages/cli/test/utils/crucible/assertions/blobsAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/blobsAssertion.ts @@ -35,7 +35,7 @@ export function createBlobsAssertion( gasLimit: "0xc350", maxPriorityFeePerGas: "0x3b9aca00", maxFeePerGas: "0x3ba26b20", - maxFeePerBlobGas: "0x3e8", + maxFeePerBlobGas: "0x3e", value: "0x10000", nonce: `0x${(nonce ?? 0).toString(16)}`, blobVersionedHashes, diff --git a/packages/cli/test/utils/crucible/assertions/defaults/attestationCountAssertion.ts b/packages/cli/test/utils/crucible/assertions/defaults/attestationCountAssertion.ts index 5d0f7a268f88..922ce80ae329 100644 --- a/packages/cli/test/utils/crucible/assertions/defaults/attestationCountAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/defaults/attestationCountAssertion.ts @@ -32,7 +32,7 @@ export const attestationsCountAssertion: Assertion<"attestationsCount", number, async assert({clock, store, epoch, node, dependantStores}) { const errors: AssertionResult[] = []; - const inclusionDelayStore = dependantStores["inclusionDelay"]; + const inclusionDelayStore = dependantStores.inclusionDelay; const startSlot = epoch === 0 ? 1 : clock.getFirstSlotOfEpoch(epoch); const endSlot = clock.getLastSlotOfEpoch(epoch); diff --git a/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts b/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts index 9659cc34e7bf..04c9b393d245 100644 --- a/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts @@ -17,7 +17,7 @@ export const nodeAssertion: Assertion<"node", {health: number; keyManagerKeys: s let keyManagerKeys: string[]; // There is an authentication issue with the lighthouse keymanager client - if (node.validator.client == ValidatorClient.Lighthouse || getAllKeys(node.validator.keys).length === 0) { + if (node.validator.client === ValidatorClient.Lighthouse || getAllKeys(node.validator.keys).length === 0) { keyManagerKeys = []; } else { const keys = (await node.validator.keyManager.listKeys()).value(); @@ -30,7 +30,7 @@ export const nodeAssertion: Assertion<"node", {health: number; keyManagerKeys: s const errors: AssertionResult[] = []; // There is an authentication issue with the lighthouse keymanager client - if (node.validator?.client == ValidatorClient.Lighthouse) return errors; + if (node.validator?.client === ValidatorClient.Lighthouse) return errors; const {health, keyManagerKeys} = store[slot]; diff --git a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts index 89802149024a..515418f7e558 100644 --- a/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts +++ b/packages/cli/test/utils/crucible/clients/beacon/lighthouse.ts @@ -109,7 +109,6 @@ export const generateLighthouseBeaconNode: BeaconNodeGenerator = (o "--verbosity", "5", ...(mining ? ["--mine", "--miner.etherbase", EL_GENESIS_ACCOUNT] : []), - ...(mode == ExecutionStartMode.PreMerge ? ["--nodiscover"] : []), + ...(mode === ExecutionStartMode.PreMerge ? ["--nodiscover"] : []), ...clientOptions, ], env: {}, diff --git a/packages/cli/test/utils/crucible/clients/validator/lodestar.ts b/packages/cli/test/utils/crucible/clients/validator/lodestar.ts index e95c39972eb0..a2b928c0a2d5 100644 --- a/packages/cli/test/utils/crucible/clients/validator/lodestar.ts +++ b/packages/cli/test/utils/crucible/clients/validator/lodestar.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {writeFile} from "node:fs/promises"; import path from "node:path"; import got from "got"; diff --git a/packages/cli/test/utils/crucible/constants.ts b/packages/cli/test/utils/crucible/constants.ts index baccab141a5e..b887b4ea4c8a 100644 --- a/packages/cli/test/utils/crucible/constants.ts +++ b/packages/cli/test/utils/crucible/constants.ts @@ -1,6 +1,5 @@ // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention export const __dirname = path.dirname(fileURLToPath(import.meta.url)); import path from "node:path"; diff --git a/packages/cli/test/utils/crucible/epochClock.ts b/packages/cli/test/utils/crucible/epochClock.ts index fc71d4accadc..0973a3da0cc3 100644 --- a/packages/cli/test/utils/crucible/epochClock.ts +++ b/packages/cli/test/utils/crucible/epochClock.ts @@ -80,7 +80,6 @@ export class EpochClock { } async waitForStartOfSlot(slot: number, silent = false): Promise { - // eslint-disable-next-line no-console if (!silent) console.log("Waiting for start of slot", {target: slot, current: this.currentSlot}); const slotTime = this.getSlotTime(slot) * MS_IN_SEC - Date.now(); await sleep(slotTime, this.signal); diff --git a/packages/cli/test/utils/crucible/externalSignerServer.ts b/packages/cli/test/utils/crucible/externalSignerServer.ts index bd8576d214c2..4282f1761db9 100644 --- a/packages/cli/test/utils/crucible/externalSignerServer.ts +++ b/packages/cli/test/utils/crucible/externalSignerServer.ts @@ -29,7 +29,6 @@ export class ExternalSignerServer { return [...this.secretKeyMap.keys()]; }); - /* eslint-disable @typescript-eslint/naming-convention */ this.server.post<{ Params: { /** BLS public key as a hex string. */ diff --git a/packages/cli/test/utils/crucible/interfaces.ts b/packages/cli/test/utils/crucible/interfaces.ts index db4b1f3b8901..84b5043ba983 100644 --- a/packages/cli/test/utils/crucible/interfaces.ts +++ b/packages/cli/test/utils/crucible/interfaces.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {ChildProcess} from "node:child_process"; import {Web3} from "web3"; import {SecretKey} from "@chainsafe/blst"; diff --git a/packages/cli/test/utils/crucible/runner/dockerRunner.ts b/packages/cli/test/utils/crucible/runner/dockerRunner.ts index cfa52c9b9ebf..bc5b97811cf4 100644 --- a/packages/cli/test/utils/crucible/runner/dockerRunner.ts +++ b/packages/cli/test/utils/crucible/runner/dockerRunner.ts @@ -22,7 +22,7 @@ export class DockerRunner implements RunnerEnv { await execChildProcess(`docker network create --subnet ${dockerNetworkIpRange}.0/24 ${dockerNetworkName}`, { logger: this.logger, }); - } catch { + } catch (_e) { // During multiple sim tests files the network might already exist } } @@ -35,7 +35,7 @@ export class DockerRunner implements RunnerEnv { logger: this.logger, }); return; - } catch { + } catch (_e) { await sleep(5000); } } diff --git a/packages/cli/test/utils/crucible/simulationTracker.ts b/packages/cli/test/utils/crucible/simulationTracker.ts index e374d3b6c328..778a7ad2a771 100644 --- a/packages/cli/test/utils/crucible/simulationTracker.ts +++ b/packages/cli/test/utils/crucible/simulationTracker.ts @@ -68,7 +68,7 @@ async function pathExists(filePath: string): Promise { try { await fs.access(filePath); return true; - } catch { + } catch (_e) { return false; } } diff --git a/packages/cli/test/utils/crucible/tableReporter.ts b/packages/cli/test/utils/crucible/tableReporter.ts index d11fc9ca3fbc..d40be13ad6e8 100644 --- a/packages/cli/test/utils/crucible/tableReporter.ts +++ b/packages/cli/test/utils/crucible/tableReporter.ts @@ -33,9 +33,8 @@ export class TableReporter extends SimulationReporter // Print slots once, may be called twice for missed block timer if (slot <= this.lastPrintedSlot) { return; - } else { - this.lastPrintedSlot = slot; } + this.lastPrintedSlot = slot; if (slot <= 0) { return; @@ -59,12 +58,10 @@ export class TableReporter extends SimulationReporter const participation: {head: number; source: number; target: number}[] = []; for (const node of nodes) { - participation.push( - stores["attestationParticipation"][node.beacon.id][slot] ?? {head: 0, source: 0, target: 0} - ); + participation.push(stores.attestationParticipation[node.beacon.id][slot] ?? {head: 0, source: 0, target: 0}); const syncCommitteeParticipation: number[] = []; for (let slot = startSlot; slot <= endSlot; slot++) { - syncCommitteeParticipation.push(stores["syncCommitteeParticipation"][node.beacon.id][slot] ?? 0); + syncCommitteeParticipation.push(stores.syncCommitteeParticipation[node.beacon.id][slot] ?? 0); } nodesSyncParticipationAvg.push(avg(syncCommitteeParticipation)); } @@ -88,19 +85,19 @@ export class TableReporter extends SimulationReporter const peersCount: number[] = []; for (const node of nodes) { - const finalized = stores["finalized"][node.beacon.id][slot]; + const finalized = stores.finalized[node.beacon.id][slot]; if (!isNullish(finalized)) finalizedSlots.push(finalized); - const inclusionDelay = stores["inclusionDelay"][node.beacon.id][slot]; + const inclusionDelay = stores.inclusionDelay[node.beacon.id][slot]; if (!isNullish(inclusionDelay)) inclusionDelays.push(inclusionDelay); - const attestationsCount = stores["attestationsCount"][node.beacon.id][slot]; + const attestationsCount = stores.attestationsCount[node.beacon.id][slot]; if (!isNullish(attestationsCount)) attestationCounts.push(attestationsCount); - const head = stores["head"][node.beacon.id][slot]; + const head = stores.head[node.beacon.id][slot]; if (!isNullish(head)) heads.push(head); - const connectedPeerCount = stores["connectedPeerCount"][node.beacon.id][slot]; + const connectedPeerCount = stores.connectedPeerCount[node.beacon.id][slot]; if (!isNullish(connectedPeerCount)) peersCount.push(connectedPeerCount); } diff --git a/packages/cli/test/utils/crucible/utils/index.ts b/packages/cli/test/utils/crucible/utils/index.ts index 0b6ae1bba1e5..0e016778cdce 100644 --- a/packages/cli/test/utils/crucible/utils/index.ts +++ b/packages/cli/test/utils/crucible/utils/index.ts @@ -73,7 +73,6 @@ export function defineSimTestConfig( additionalSlots: opts.additionalSlotsForTTD ?? 2, }); - /* eslint-disable @typescript-eslint/naming-convention */ const forkConfig = createChainForkConfig({ ...opts, GENESIS_DELAY: genesisDelaySeconds, @@ -157,7 +156,7 @@ export const arrayGroupBy = ( ): Record => array.reduce( (acc, value, index, array) => { - (acc[predicate(value, index, array)] ||= []).push(value); + acc[predicate(value, index, array)]?.push(value); return acc; }, {} as {[key: string]: T[]} diff --git a/packages/cli/test/utils/crucible/utils/keys.ts b/packages/cli/test/utils/crucible/utils/keys.ts index 27983c04cd38..8915e85b529e 100644 --- a/packages/cli/test/utils/crucible/utils/keys.ts +++ b/packages/cli/test/utils/crucible/utils/keys.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {readFile, writeFile} from "node:fs/promises"; import path from "node:path"; import yaml from "js-yaml"; diff --git a/packages/cli/test/utils/crucible/utils/network.ts b/packages/cli/test/utils/crucible/utils/network.ts index e3555fc149b9..a9a70b5561b4 100644 --- a/packages/cli/test/utils/crucible/utils/network.ts +++ b/packages/cli/test/utils/crucible/utils/network.ts @@ -74,14 +74,12 @@ 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()).value(); if (!result.isSyncing) { break; - } else { - await sleep(1000, env.options.controller.signal); } + await sleep(1000, env.options.controller.signal); } } diff --git a/packages/cli/test/utils/crucible/utils/paths.ts b/packages/cli/test/utils/crucible/utils/paths.ts index 9f0628e34656..4ff149d6f308 100644 --- a/packages/cli/test/utils/crucible/utils/paths.ts +++ b/packages/cli/test/utils/crucible/utils/paths.ts @@ -79,10 +79,15 @@ export const getNodeMountedPaths = => { return Object.entries(paths) - .map(([key, value]) => [ + .flatMap(([key, value]) => [ [key, value], [`${key}Mounted`, mount ? (value as string).replace(paths.rootDir, mountPath) : value], ]) - .flat() - .reduce((o, [key, value]) => ({...o, [key]: value as string}), {}) as MountedPaths; + .reduce( + (o, [key, value]) => { + o[key] = value as string; + return o; + }, + {} as Record + ) as MountedPaths; }; diff --git a/packages/cli/test/utils/crucible/utils/syncing.ts b/packages/cli/test/utils/crucible/utils/syncing.ts index 4a8913105335..b720c6bf6ccc 100644 --- a/packages/cli/test/utils/crucible/utils/syncing.ts +++ b/packages/cli/test/utils/crucible/utils/syncing.ts @@ -148,7 +148,9 @@ export async function assertUnknownBlockSync(env: Simulation): Promise { } catch (error) { if (!(error as Error).message.includes("BLOCK_ERROR_PARENT_UNKNOWN")) { env.tracker.record({ - message: `Publishing unknown block should return "BLOCK_ERROR_PARENT_UNKNOWN" got "${(error as Error).message}"`, + message: `Publishing unknown block should return "BLOCK_ERROR_PARENT_UNKNOWN" got "${ + (error as Error).message + }"`, slot: env.clock.currentSlot, assertionId: "unknownBlockParent", }); @@ -178,13 +180,11 @@ 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()).value(); if (!result.isSyncing) { break; - } else { - await sleep(1000, env.options.controller.signal); } + await sleep(1000, env.options.controller.signal); } } diff --git a/packages/cli/test/utils/mockBeaconApiServer.ts b/packages/cli/test/utils/mockBeaconApiServer.ts index 80ce282e102c..e7e80338686f 100644 --- a/packages/cli/test/utils/mockBeaconApiServer.ts +++ b/packages/cli/test/utils/mockBeaconApiServer.ts @@ -5,7 +5,6 @@ import {ChainForkConfig} from "@lodestar/config"; import {config} from "@lodestar/config/default"; import {ssz} from "@lodestar/types"; import {fromHex, toHex} from "@lodestar/utils"; -// eslint-disable-next-line import/no-relative-packages import {testLogger} from "../../../beacon-node/test/utils/logger.js"; const ZERO_HASH_HEX = toHex(Buffer.alloc(32, 0)); diff --git a/packages/config/package.json b/packages/config/package.json index adf21542a890..aadbf9e004b2 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/config", - "version": "1.22.0", + "version": "1.23.0", "description": "Chain configuration required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -47,8 +47,8 @@ "build:watch": "yarn run build --watch", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit", "test:unit": "yarn vitest --run --dir test/unit/", "check-readme": "typescript-docs-verifier" @@ -64,8 +64,9 @@ "blockchain" ], "dependencies": { - "@chainsafe/ssz": "^0.17.1", - "@lodestar/params": "^1.22.0", - "@lodestar/types": "^1.22.0" + "@chainsafe/ssz": "^0.18.0", + "@lodestar/params": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0" } } diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 741ddc99f8cd..5da5f8d2acb0 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; +import {fromHex as b} from "@lodestar/utils"; import {PresetName} from "@lodestar/params"; import {ChainConfig} from "../types.js"; diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index 26f49cc3e47d..f1a52e956f4d 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; +import {fromHex as b} from "@lodestar/utils"; import {PresetName} from "@lodestar/params"; import {ChainConfig} from "../types.js"; diff --git a/packages/config/src/chainConfig/default.ts b/packages/config/src/chainConfig/default.ts index d778c9b82447..ef2b9743a1d2 100644 --- a/packages/config/src/chainConfig/default.ts +++ b/packages/config/src/chainConfig/default.ts @@ -10,9 +10,10 @@ switch (ACTIVE_PRESET) { defaultChainConfig = minimal; break; case PresetName.mainnet: - default: defaultChainConfig = mainnet; break; + default: + defaultChainConfig = mainnet; } export {defaultChainConfig}; diff --git a/packages/config/src/chainConfig/json.ts b/packages/config/src/chainConfig/json.ts index 4e61333cbdee..78db9230c836 100644 --- a/packages/config/src/chainConfig/json.ts +++ b/packages/config/src/chainConfig/json.ts @@ -1,4 +1,4 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; +import {fromHex, toHex} from "@lodestar/utils"; import {ChainConfig, chainConfigTypes, SpecValue, SpecValueTypeName} from "./types.js"; const MAX_UINT64_JSON = "18446744073709551615"; @@ -69,7 +69,7 @@ export function serializeSpecValue(value: SpecValue, typeName: SpecValueTypeName if (!(value instanceof Uint8Array)) { throw Error(`Invalid value ${value.toString()} expected Uint8Array`); } - return toHexString(value); + return toHex(value); case "string": if (typeof value !== "string") { @@ -95,7 +95,7 @@ export function deserializeSpecValue(valueStr: unknown, typeName: SpecValueTypeN return BigInt(valueStr); case "bytes": - return fromHexString(valueStr); + return fromHex(valueStr); case "string": return valueStr; diff --git a/packages/config/src/chainConfig/networks/chiado.ts b/packages/config/src/chainConfig/networks/chiado.ts index 43b13a210dac..096942af672c 100644 --- a/packages/config/src/chainConfig/networks/chiado.ts +++ b/packages/config/src/chainConfig/networks/chiado.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; +import {fromHex as b} from "@lodestar/utils"; import {ChainConfig} from "../types.js"; import {gnosisChainConfig as gnosis} from "./gnosis.js"; diff --git a/packages/config/src/chainConfig/networks/ephemery.ts b/packages/config/src/chainConfig/networks/ephemery.ts index 29e3f7b92d01..d1c17c36f0ab 100644 --- a/packages/config/src/chainConfig/networks/ephemery.ts +++ b/packages/config/src/chainConfig/networks/ephemery.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; +import {fromHex as b} from "@lodestar/utils"; import {ChainConfig} from "../types.js"; import {chainConfig as mainnet} from "../configs/mainnet.js"; diff --git a/packages/config/src/chainConfig/networks/gnosis.ts b/packages/config/src/chainConfig/networks/gnosis.ts index 8034e478fd28..a25162ae5028 100644 --- a/packages/config/src/chainConfig/networks/gnosis.ts +++ b/packages/config/src/chainConfig/networks/gnosis.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; +import {fromHex as b} from "@lodestar/utils"; import {PresetName} from "@lodestar/params"; import {ChainConfig} from "../types.js"; import {chainConfig as mainnet} from "../configs/mainnet.js"; diff --git a/packages/config/src/chainConfig/networks/holesky.ts b/packages/config/src/chainConfig/networks/holesky.ts index 187543b871f2..46c32606931f 100644 --- a/packages/config/src/chainConfig/networks/holesky.ts +++ b/packages/config/src/chainConfig/networks/holesky.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; +import {fromHex as b} from "@lodestar/utils"; import {ChainConfig} from "../types.js"; import {chainConfig as mainnet} from "../configs/mainnet.js"; diff --git a/packages/config/src/chainConfig/networks/mainnet.ts b/packages/config/src/chainConfig/networks/mainnet.ts index 24584ad8442b..13225b463da9 100644 --- a/packages/config/src/chainConfig/networks/mainnet.ts +++ b/packages/config/src/chainConfig/networks/mainnet.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; +import {fromHex as b} from "@lodestar/utils"; import {ChainConfig} from "../types.js"; import {chainConfig as mainnet} from "../configs/mainnet.js"; diff --git a/packages/config/src/chainConfig/networks/sepolia.ts b/packages/config/src/chainConfig/networks/sepolia.ts index 51102cfafa7d..e7e5dc76f691 100644 --- a/packages/config/src/chainConfig/networks/sepolia.ts +++ b/packages/config/src/chainConfig/networks/sepolia.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString as b} from "@chainsafe/ssz"; +import {fromHex as b} from "@lodestar/utils"; import {ChainConfig} from "../types.js"; import {chainConfig as mainnet} from "../configs/mainnet.js"; diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 05fff02f2eaf..5c06b205c2f6 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -1,7 +1,5 @@ import {PresetName} from "@lodestar/params"; -/* eslint-disable @typescript-eslint/naming-convention */ - /** * Run-time chain configuration */ diff --git a/packages/config/src/genesisConfig/index.ts b/packages/config/src/genesisConfig/index.ts index 52fdd03880a6..d2dfae2a8e08 100644 --- a/packages/config/src/genesisConfig/index.ts +++ b/packages/config/src/genesisConfig/index.ts @@ -1,6 +1,6 @@ -import {toHexString} from "@chainsafe/ssz"; import {ForkName, SLOTS_PER_EPOCH, DOMAIN_VOLUNTARY_EXIT} from "@lodestar/params"; import {DomainType, ForkDigest, phase0, Root, Slot, ssz, Version} from "@lodestar/types"; +import {toHex} from "@lodestar/utils"; import {ChainForkConfig} from "../beaconConfig.js"; import {ForkDigestHex, CachedGenesis} from "./types.js"; export type {ForkDigestContext} from "./types.js"; @@ -139,7 +139,7 @@ function computeForkDataRoot(currentVersion: Version, genesisValidatorsRoot: Roo } function toHexStringNoPrefix(hex: string | Uint8Array): string { - return strip0xPrefix(typeof hex === "string" ? hex : toHexString(hex)); + return strip0xPrefix(typeof hex === "string" ? hex : toHex(hex)); } function strip0xPrefix(hex: string): string { diff --git a/packages/config/test/unit/index.test.ts b/packages/config/test/unit/index.test.ts index a6fca7ad643a..bde31f6b1477 100644 --- a/packages/config/test/unit/index.test.ts +++ b/packages/config/test/unit/index.test.ts @@ -31,7 +31,6 @@ describe("forks", () => { }); it("correctly handle pre-genesis", () => { - // eslint-disable-next-line @typescript-eslint/naming-convention const postMergeTestnet = createForkConfig({...chainConfig, ALTAIR_FORK_EPOCH: 0, BELLATRIX_FORK_EPOCH: 0}); expect(postMergeTestnet.getForkName(-1)).toBe(ForkName.bellatrix); }); diff --git a/packages/db/package.json b/packages/db/package.json index 0e3a2c847d4b..370caaa5f8c9 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/db", - "version": "1.22.0", + "version": "1.23.0", "description": "DB modules of Lodestar", "author": "ChainSafe Systems", "homepage": "https://github.com/ChainSafe/lodestar#readme", @@ -28,20 +28,20 @@ "build:release": "yarn clean && yarn run build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit", "test:unit": "vitest --run --dir test/unit/", "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/ssz": "^0.17.1", - "@lodestar/config": "^1.22.0", - "@lodestar/utils": "^1.22.0", + "@chainsafe/ssz": "^0.18.0", + "@lodestar/config": "^1.23.0", + "@lodestar/utils": "^1.23.0", "classic-level": "^1.4.1", "it-all": "^3.0.4" }, "devDependencies": { - "@lodestar/logger": "^1.22.0" + "@lodestar/logger": "^1.23.0" } } diff --git a/packages/db/src/abstractRepository.ts b/packages/db/src/abstractRepository.ts index e0462d162ddc..5d5b0763ba26 100644 --- a/packages/db/src/abstractRepository.ts +++ b/packages/db/src/abstractRepository.ts @@ -142,7 +142,7 @@ export abstract class Repository { async batchRemove(values: T[]): Promise { // handle single value in batchDelete - await this.batchDelete(Array.from({length: values.length}, (ignored, i) => this.getId(values[i]))); + await this.batchDelete(Array.from({length: values.length}, (_ignored, i) => this.getId(values[i]))); } async keys(opts?: FilterOptions): Promise { diff --git a/packages/db/src/controller/level.ts b/packages/db/src/controller/level.ts index 084b577b6a01..82fbf631f7ae 100644 --- a/packages/db/src/controller/level.ts +++ b/packages/db/src/controller/level.ts @@ -78,11 +78,11 @@ export class LevelDbController implements DatabaseController { if (res?.startsWith("Usage: gdu ")) { return "gdu"; } - } catch { + } catch (_e) { /* eslint-disable no-console */ console.error("Cannot find gdu command, falling back to du"); } diff --git a/packages/flare/package.json b/packages/flare/package.json index 3a11e9b49ebf..8239eec52e2a 100644 --- a/packages/flare/package.json +++ b/packages/flare/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/flare", - "version": "1.22.0", + "version": "1.23.0", "description": "Beacon chain debugging tool", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -41,8 +41,8 @@ "build:watch": "yarn run build --watch", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\" flare --help", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit", "test:unit": "vitest --run --dir test/unit/", "check-readme": "typescript-docs-verifier" @@ -59,13 +59,13 @@ ], "dependencies": { "@chainsafe/bls-keygen": "^0.4.0", - "@chainsafe/blst": "^2.0.3", - "@lodestar/api": "^1.22.0", - "@lodestar/config": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/state-transition": "^1.22.0", - "@lodestar/types": "^1.22.0", - "@lodestar/utils": "^1.22.0", + "@chainsafe/blst": "^2.2.0", + "@lodestar/api": "^1.23.0", + "@lodestar/config": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/state-transition": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0", "source-map-support": "^0.5.21", "yargs": "^17.7.1" }, diff --git a/packages/flare/src/cmds/selfSlashAttester.ts b/packages/flare/src/cmds/selfSlashAttester.ts index a37c6c765bd3..174535ba2e7d 100644 --- a/packages/flare/src/cmds/selfSlashAttester.ts +++ b/packages/flare/src/cmds/selfSlashAttester.ts @@ -52,7 +52,7 @@ export async function selfSlashAttesterHandler(args: SelfSlashArgs): Promise 0: ${batchSize}`); if (batchSize > MAX_VALIDATORS_PER_COMMITTEE) throw Error("batchSize must be < MAX_VALIDATORS_PER_COMMITTEE"); @@ -79,7 +79,7 @@ export async function selfSlashAttesterHandler(args: SelfSlashArgs): Promise sk.toPublicKey().toHex()); - const validators = (await client.beacon.getStateValidators({stateId: "head", validatorIds: pksHex})).value(); + const validators = (await client.beacon.postStateValidators({stateId: "head", validatorIds: pksHex})).value(); // All validators in the batch will be part of the same AttesterSlashing const attestingIndices = validators.map((v) => v.index); @@ -92,7 +92,7 @@ export async function selfSlashAttesterHandler(args: SelfSlashArgs): Promise 0: ${batchSize}`); // TODO: Ask the user to confirm the range and slash action @@ -77,7 +77,7 @@ export async function selfSlashProposerHandler(args: SelfSlashArgs): Promise sk.toPublicKey().toHex()); - const validators = (await client.beacon.getStateValidators({stateId: "head", validatorIds: pksHex})).value(); + const validators = (await client.beacon.postStateValidators({stateId: "head", validatorIds: pksHex})).value(); // Submit all ProposerSlashing for range at once await Promise.all( @@ -88,7 +88,7 @@ export async function selfSlashProposerHandler(args: SelfSlashArgs): Promise parseInt(n)); - if (isNaN(from)) throw Error(`Invalid range from isNaN '${range}'`); - if (isNaN(to)) throw Error(`Invalid range to isNaN '${range}'`); + if (Number.isNaN(from)) throw Error(`Invalid range from isNaN '${range}'`); + if (Number.isNaN(to)) throw Error(`Invalid range to isNaN '${range}'`); if (from > to) throw Error(`Invalid range from > to '${range}'`); const arr: number[] = []; diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index 728036b365d3..1ee9d043c925 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.22.0", + "version": "1.23.0", "type": "module", "exports": "./lib/index.js", "types": "./lib/index.d.ts", @@ -29,19 +29,19 @@ "build:release": "yarn clean && yarn run build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit", "test:unit": "vitest --run --dir test/unit/", "check-readme": "typescript-docs-verifier" }, "dependencies": { - "@chainsafe/ssz": "^0.17.1", - "@lodestar/config": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/state-transition": "^1.22.0", - "@lodestar/types": "^1.22.0", - "@lodestar/utils": "^1.22.0" + "@chainsafe/ssz": "^0.18.0", + "@lodestar/config": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/state-transition": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0" }, "keywords": [ "ethereum", diff --git a/packages/fork-choice/src/forkChoice/errors.ts b/packages/fork-choice/src/forkChoice/errors.ts index ea0c6305ba42..43d209106eed 100644 --- a/packages/fork-choice/src/forkChoice/errors.ts +++ b/packages/fork-choice/src/forkChoice/errors.ts @@ -95,8 +95,4 @@ export type ForkChoiceErrorType = | {code: ForkChoiceErrorCode.UNABLE_TO_SET_JUSTIFIED_CHECKPOINT; error: Error} | {code: ForkChoiceErrorCode.AFTER_BLOCK_FAILED; error: Error}; -export class ForkChoiceError extends LodestarError { - constructor(type: ForkChoiceErrorType) { - super(type); - } -} +export class ForkChoiceError extends LodestarError {} diff --git a/packages/fork-choice/src/forkChoice/forkChoice.ts b/packages/fork-choice/src/forkChoice/forkChoice.ts index 2405a442c8cd..dcc76441865e 100644 --- a/packages/fork-choice/src/forkChoice/forkChoice.ts +++ b/packages/fork-choice/src/forkChoice/forkChoice.ts @@ -1,5 +1,4 @@ -import {toHexString} from "@chainsafe/ssz"; -import {Logger, fromHex, toRootHex} from "@lodestar/utils"; +import {Logger, MapDef, fromHex, toRootHex} from "@lodestar/utils"; import {SLOTS_PER_HISTORICAL_ROOT, SLOTS_PER_EPOCH, INTERVALS_PER_SLOT} from "@lodestar/params"; import {bellatrix, Slot, ValidatorIndex, phase0, ssz, RootHex, Epoch, Root, BeaconBlock} from "@lodestar/types"; import { @@ -35,7 +34,6 @@ import {ForkChoiceError, ForkChoiceErrorCode, InvalidBlockCode, InvalidAttestati import { IForkChoice, LatestMessage, - QueuedAttestation, PowBlockHex, EpochDifference, AncestorResult, @@ -92,7 +90,15 @@ export class ForkChoice implements IForkChoice { * Attestations that arrived at the current slot and must be queued for later processing. * NOT currently tracked in the protoArray */ - private readonly queuedAttestations = new Set(); + private readonly queuedAttestations: MapDef>> = new MapDef( + () => new MapDef(() => new Set()) + ); + + /** + * It's inconsistent to count number of queued attestations at different intervals of slot. + * Instead of that, we count number of queued attestations at the previous slot. + */ + private queuedAttestationsPreviousSlot = 0; // Note: as of Jun 2022 Lodestar metrics show that 100% of the times updateHead() is called, synced = false. // Because we are processing attestations from gossip, recomputing scores is always necessary @@ -131,7 +137,7 @@ export class ForkChoice implements IForkChoice { getMetrics(): ForkChoiceMetrics { return { votes: this.votes.length, - queuedAttestations: this.queuedAttestations.size, + queuedAttestations: this.queuedAttestationsPreviousSlot, validatedAttestationDatas: this.validatedAttestationDatas.size, balancesLength: this.balances.length, nodes: this.protoArray.nodes.length, @@ -199,6 +205,7 @@ export class ForkChoice implements IForkChoice { return {head, isHeadTimely, notReorgedReason}; } case UpdateHeadOpt.GetCanonicialHead: + return {head: canonicialHeadBlock}; default: return {head: canonicialHeadBlock}; } @@ -414,7 +421,8 @@ export class ForkChoice implements IForkChoice { }); } - return (this.head = headNode); + this.head = headNode; + return this.head; } /** @@ -643,7 +651,7 @@ export class ForkChoice implements IForkChoice { ...(isExecutionBlockBodyType(block.body) && isExecutionStateType(state) && isExecutionEnabled(state, block) ? { - executionPayloadBlockHash: toHexString(block.body.executionPayload.blockHash), + executionPayloadBlockHash: toRootHex(block.body.executionPayload.blockHash), executionPayloadNumber: block.body.executionPayload.blockNumber, executionStatus: this.getPostMergeExecStatus(executionStatus), dataAvailabilityStatus, @@ -715,12 +723,13 @@ export class ForkChoice implements IForkChoice { // Attestations can only affect the fork choice of subsequent slots. // Delay consideration in the fork choice until their slot is in the past. // ``` - this.queuedAttestations.add({ - slot: slot, - attestingIndices: attestation.attestingIndices, - blockRoot: blockRootHex, - targetEpoch, - }); + const byRoot = this.queuedAttestations.getOrDefault(slot); + const validatorIndices = byRoot.getOrDefault(blockRootHex); + for (const validatorIndex of attestation.attestingIndices) { + if (!this.fcStore.equivocatingIndices.has(validatorIndex)) { + validatorIndices.add(validatorIndex); + } + } } } @@ -750,6 +759,11 @@ export class ForkChoice implements IForkChoice { /** * Call `onTick` for all slots between `fcStore.getCurrentSlot()` and the provided `currentSlot`. + * This should only be called once per slot because: + * - calling this multiple times in the same slot does not update `votes` + * - new attestations in the current slot must stay in the queue + * - new attestations in the old slots are applied to the `votes` already + * - also side effect of this function is `validatedAttestationDatas` reseted */ updateTime(currentSlot: Slot): void { if (this.fcStore.currentSlot >= currentSlot) return; @@ -759,6 +773,7 @@ export class ForkChoice implements IForkChoice { this.onTick(previousSlot + 1); } + this.queuedAttestationsPreviousSlot = 0; // Process any attestations that might now be eligible. this.processAttestationQueue(); this.validatedAttestationDatas = new Set(); @@ -1233,7 +1248,9 @@ export class ForkChoice implements IForkChoice { currentEpoch: epochNow, }, }); - } else if (!forceImport && targetEpoch + 1 < epochNow) { + } + + if (!forceImport && targetEpoch + 1 < epochNow) { throw new ForkChoiceError({ code: ForkChoiceErrorCode.INVALID_ATTESTATION, err: { @@ -1351,15 +1368,23 @@ export class ForkChoice implements IForkChoice { */ private processAttestationQueue(): void { const currentSlot = this.fcStore.currentSlot; - for (const attestation of this.queuedAttestations.values()) { - // Delay consideration in the fork choice until their slot is in the past. - if (attestation.slot < currentSlot) { - this.queuedAttestations.delete(attestation); - const {blockRoot, targetEpoch} = attestation; - const blockRootHex = blockRoot; - for (const validatorIndex of attestation.attestingIndices) { - this.addLatestMessage(validatorIndex, targetEpoch, blockRootHex); + for (const [slot, byRoot] of this.queuedAttestations.entries()) { + const targetEpoch = computeEpochAtSlot(slot); + if (slot < currentSlot) { + this.queuedAttestations.delete(slot); + for (const [blockRoot, validatorIndices] of byRoot.entries()) { + const blockRootHex = blockRoot; + for (const validatorIndex of validatorIndices) { + // equivocatingIndices was checked in onAttestation + this.addLatestMessage(validatorIndex, targetEpoch, blockRootHex); + } + + if (slot === currentSlot - 1) { + this.queuedAttestationsPreviousSlot += validatorIndices.size; + } } + } else { + break; } } } @@ -1484,7 +1509,7 @@ export function assertValidTerminalPowBlock( // powBock.blockHash is hex, so we just pick the corresponding root if (!ssz.Root.equals(block.body.executionPayload.parentHash, config.TERMINAL_BLOCK_HASH)) throw new Error( - `Invalid terminal block hash, expected: ${toHexString(config.TERMINAL_BLOCK_HASH)}, actual: ${toHexString( + `Invalid terminal block hash, expected: ${toRootHex(config.TERMINAL_BLOCK_HASH)}, actual: ${toRootHex( block.body.executionPayload.parentHash )}` ); @@ -1510,7 +1535,9 @@ export function assertValidTerminalPowBlock( throw Error( `Invalid terminal POW block: total difficulty not reached expected >= ${config.TERMINAL_TOTAL_DIFFICULTY}, actual = ${powBlock.totalDifficulty}` ); - } else if (!isParentTotalDifficultyValid) { + } + + if (!isParentTotalDifficultyValid) { throw Error( `Invalid terminal POW block parent: expected < ${config.TERMINAL_TOTAL_DIFFICULTY}, actual = ${powBlockParent.totalDifficulty}` ); diff --git a/packages/fork-choice/src/forkChoice/interface.ts b/packages/fork-choice/src/forkChoice/interface.ts index 0b6d56a88bf2..9ac8cdfac81b 100644 --- a/packages/fork-choice/src/forkChoice/interface.ts +++ b/packages/fork-choice/src/forkChoice/interface.ts @@ -250,14 +250,3 @@ export type LatestMessage = { epoch: Epoch; root: RootHex; }; - -/** - * Used for queuing attestations from the current slot. Only contains the minimum necessary - * information about the attestation. - */ -export type QueuedAttestation = { - slot: Slot; - attestingIndices: ValidatorIndex[]; - blockRoot: RootHex; - targetEpoch: Epoch; -}; diff --git a/packages/fork-choice/src/forkChoice/store.ts b/packages/fork-choice/src/forkChoice/store.ts index d16e021529db..2cd4da512870 100644 --- a/packages/fork-choice/src/forkChoice/store.ts +++ b/packages/fork-choice/src/forkChoice/store.ts @@ -55,18 +55,22 @@ export class ForkChoiceStore implements IForkChoiceStore { private _finalizedCheckpoint: CheckpointWithHex; unrealizedFinalizedCheckpoint: CheckpointWithHex; equivocatingIndices = new Set(); + justifiedBalancesGetter: JustifiedBalancesGetter; + currentSlot: Slot; constructor( - public currentSlot: Slot, + currentSlot: Slot, justifiedCheckpoint: phase0.Checkpoint, finalizedCheckpoint: phase0.Checkpoint, justifiedBalances: EffectiveBalanceIncrements, - public justifiedBalancesGetter: JustifiedBalancesGetter, + justifiedBalancesGetter: JustifiedBalancesGetter, private readonly events?: { onJustified: (cp: CheckpointWithHex) => void; onFinalized: (cp: CheckpointWithHex) => void; } ) { + this.justifiedBalancesGetter = justifiedBalancesGetter; + this.currentSlot = currentSlot; const justified = { checkpoint: toCheckpointWithHex(justifiedCheckpoint), balances: justifiedBalances, diff --git a/packages/fork-choice/src/protoArray/computeDeltas.ts b/packages/fork-choice/src/protoArray/computeDeltas.ts index 8301c1e77a75..c921e12f751e 100644 --- a/packages/fork-choice/src/protoArray/computeDeltas.ts +++ b/packages/fork-choice/src/protoArray/computeDeltas.ts @@ -3,6 +3,9 @@ import {EffectiveBalanceIncrements} from "@lodestar/state-transition"; import {VoteTracker} from "./interface.js"; import {ProtoArrayError, ProtoArrayErrorCode} from "./errors.js"; +// reuse arrays to avoid memory reallocation and gc +const deltas = new Array(); + /** * Returns a list of `deltas`, where there is one delta for each of the indices in `indices` * @@ -19,14 +22,12 @@ export function computeDeltas( newBalances: EffectiveBalanceIncrements, equivocatingIndices: Set ): number[] { - const deltas = new Array(numProtoNodes); - for (let i = 0; i < numProtoNodes; i++) { - deltas[i] = 0; - } + deltas.length = numProtoNodes; + deltas.fill(0); // avoid creating new variables in the loop to potentially reduce GC pressure - let oldBalance, newBalance: number; - let currentIndex, nextIndex: number | null; + let oldBalance: number, newBalance: number; + let currentIndex: number | null, nextIndex: number | null; for (let vIndex = 0; vIndex < votes.length; vIndex++) { const vote = votes[vIndex]; @@ -47,7 +48,7 @@ export function computeDeltas( // It is possible that there was a vote for an unknown validator if we change our justified // state to a new state with a higher epoch that is on a different fork because that fork may have // on-boarded fewer validators than the prior fork. - newBalance = newBalances[vIndex] ?? 0; + newBalance = newBalances === oldBalances ? oldBalance : (newBalances[vIndex] ?? 0); if (equivocatingIndices.size > 0 && equivocatingIndices.has(vIndex)) { // this function could be called multiple times but we only want to process slashing validator for 1 time diff --git a/packages/fork-choice/src/protoArray/errors.ts b/packages/fork-choice/src/protoArray/errors.ts index 2e044de154e1..650fd1c0b244 100644 --- a/packages/fork-choice/src/protoArray/errors.ts +++ b/packages/fork-choice/src/protoArray/errors.ts @@ -56,8 +56,4 @@ export type ProtoArrayErrorType = | {code: ProtoArrayErrorCode.INVALID_JUSTIFIED_EXECUTION_STATUS; root: RootHex} | ({code: ProtoArrayErrorCode.INVALID_LVH_EXECUTION_RESPONSE} & LVHExecError); -export class ProtoArrayError extends LodestarError { - constructor(type: ProtoArrayErrorType) { - super(type); - } -} +export class ProtoArrayError extends LodestarError {} diff --git a/packages/fork-choice/src/protoArray/protoArray.ts b/packages/fork-choice/src/protoArray/protoArray.ts index eaa86b2f0ee1..82a8b2620880 100644 --- a/packages/fork-choice/src/protoArray/protoArray.ts +++ b/packages/fork-choice/src/protoArray/protoArray.ts @@ -1,8 +1,8 @@ -import {toHexString} from "@chainsafe/ssz"; import {Epoch, RootHex, Slot} from "@lodestar/types"; import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {GENESIS_EPOCH} from "@lodestar/params"; +import {toRootHex} from "@lodestar/utils"; import {ForkChoiceError, ForkChoiceErrorCode} from "../forkChoice/errors.js"; import {ProtoBlock, ProtoNode, HEX_ZERO_HASH, ExecutionStatus, LVHExecResponse} from "./interface.js"; import {ProtoArrayError, ProtoArrayErrorCode, LVHExecError, LVHExecErrorCode} from "./errors.js"; @@ -10,7 +10,7 @@ import {ProtoArrayError, ProtoArrayErrorCode, LVHExecError, LVHExecErrorCode} fr export const DEFAULT_PRUNE_THRESHOLD = 0; type ProposerBoost = {root: RootHex; score: number}; -const ZERO_HASH_HEX = toHexString(Buffer.alloc(32, 0)); +const ZERO_HASH_HEX = toRootHex(Buffer.alloc(32, 0)); export class ProtoArray { // Do not attempt to prune the tree unless it has at least this many nodes. @@ -304,9 +304,9 @@ export class ProtoArray { * EL is lazy (or buggy) with its LVH response. */ throw Error(`Unable to find latestValidExecHash=${latestValidExecHash} in the forkchoice`); - } else { - this.propagateInValidExecutionStatusByIndex(invalidateFromParentIndex, latestValidHashIndex, currentSlot); } + + this.propagateInValidExecutionStatusByIndex(invalidateFromParentIndex, latestValidHashIndex, currentSlot); } } @@ -431,7 +431,9 @@ export class ProtoArray { code: ProtoArrayErrorCode.INVALID_LVH_EXECUTION_RESPONSE, ...this.lvhError, }); - } else if (validNode.executionStatus === ExecutionStatus.Syncing) { + } + + if (validNode.executionStatus === ExecutionStatus.Syncing) { validNode.executionStatus = ExecutionStatus.Valid; } return validNode; @@ -808,10 +810,9 @@ export class ProtoArray { descendantRoot: blockRoot, ancestorSlot, }); - } else { - // Root is older or equal than queried slot, thus a skip slot. Return most recent root prior to slot. - return blockRoot; } + // Root is older or equal than queried slot, thus a skip slot. Return most recent root prior to slot. + return blockRoot; } /** @@ -964,7 +965,6 @@ export class ProtoArray { * Returns a common ancestor for nodeA or nodeB or null if there's none */ getCommonAncestor(nodeA: ProtoNode, nodeB: ProtoNode): ProtoNode | null { - // eslint-disable-next-line no-constant-condition while (true) { // If nodeA is higher than nodeB walk up nodeA tree if (nodeA.slot > nodeB.slot) { diff --git a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts index 40988a0e4a71..f47849136bff 100644 --- a/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/forkChoice.test.ts @@ -14,11 +14,9 @@ import { EpochDifference, DataAvailabilityStatus, } from "../../../src/index.js"; +import {getBlockRoot, getStateRoot} from "../../utils/index.js"; -const rootStateBytePrefix = 0xaa; -const rootBlockBytePrefix = 0xbb; - -describe("Forkchoice", function () { +describe("Forkchoice", () => { const genesisSlot = 0; const genesisEpoch = 0; const genesisRoot = "0x0000000000000000000000000000000000000000000000000000000000000000"; @@ -118,7 +116,7 @@ describe("Forkchoice", function () { } }; - it("getAllAncestorBlocks", function () { + it("getAllAncestorBlocks", () => { // Add block that is a finalized descendant. const block = getBlock(genesisSlot + 1); protoArr.onBlock(block, block.slot); @@ -175,20 +173,6 @@ describe("Forkchoice", function () { // TODO: more unit tests for other apis }); -export function getStateRoot(slot: number): RootHex { - const root = Buffer.alloc(32, 0x00); - root[0] = rootStateBytePrefix; - root[31] = slot; - return toHex(root); -} - -export function getBlockRoot(slot: number): RootHex { - const root = Buffer.alloc(32, 0x00); - root[0] = rootBlockBytePrefix; - root[31] = slot; - return toHex(root); -} - function range(from: number, toInclusive: number): number[] { const arr: number[] = []; for (let i = from; i <= toInclusive; i++) { diff --git a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts index f603a4069b83..187834e6d63a 100644 --- a/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/getProposerHead.test.ts @@ -13,11 +13,11 @@ import { DataAvailabilityStatus, } from "../../../src/index.js"; import {NotReorgedReason} from "../../../src/forkChoice/interface.js"; -import {getBlockRoot, getStateRoot} from "./forkChoice.test.js"; +import {getBlockRoot, getStateRoot} from "../../utils/index.js"; type ProtoBlockWithWeight = ProtoBlock & {weight: number}; // weight of the block itself -describe("Forkchoice / GetProposerHead", function () { +describe("Forkchoice / GetProposerHead", () => { const genesisSlot = 0; const genesisEpoch = 0; const genesisRoot = "0x0000000000000000000000000000000000000000000000000000000000000000"; diff --git a/packages/fork-choice/test/unit/forkChoice/utils.test.ts b/packages/fork-choice/test/unit/forkChoice/utils.test.ts index 3f315d079842..44b0eb8b719b 100644 --- a/packages/fork-choice/test/unit/forkChoice/utils.test.ts +++ b/packages/fork-choice/test/unit/forkChoice/utils.test.ts @@ -3,12 +3,11 @@ import {createChainForkConfig} from "@lodestar/config"; import {ssz} from "@lodestar/types"; import {assertValidTerminalPowBlock, ExecutionStatus} from "../../../src/index.js"; -describe("assertValidTerminalPowBlock", function () { - // eslint-disable-next-line @typescript-eslint/naming-convention +describe("assertValidTerminalPowBlock", () => { const config = createChainForkConfig({TERMINAL_TOTAL_DIFFICULTY: BigInt(10)}); const block = ssz.bellatrix.BeaconBlock.defaultValue(); const executionStatus = ExecutionStatus.Valid; - it("should accept ttd >= genesis block as terminal without powBlockParent", function () { + it("should accept ttd >= genesis block as terminal without powBlockParent", () => { const powBlock = { blockHash: "0x" + "ab".repeat(32), // genesis powBlock will have zero parent hash @@ -20,7 +19,7 @@ describe("assertValidTerminalPowBlock", function () { ).not.toThrow(); }); - it("should require powBlockParent if powBlock not genesis", function () { + it("should require powBlockParent if powBlock not genesis", () => { const powBlock = { blockHash: "0x" + "ab".repeat(32), // genesis powBlock will have non zero parent hash @@ -32,7 +31,7 @@ describe("assertValidTerminalPowBlock", function () { ).toThrow(); }); - it("should require powBlock >= ttd", function () { + it("should require powBlock >= ttd", () => { const powBlock = { blockHash: "0x" + "ab".repeat(32), // genesis powBlock will have non zero parent hash @@ -44,7 +43,7 @@ describe("assertValidTerminalPowBlock", function () { ).toThrow(); }); - it("should require powBlockParent < ttd", function () { + it("should require powBlockParent < ttd", () => { const powBlock = { blockHash: "0x" + "ab".repeat(32), // genesis powBlock will have non zero parent hash @@ -56,7 +55,7 @@ describe("assertValidTerminalPowBlock", function () { ).toThrow(); }); - it("should accept powBlockParent < ttd and powBlock >= ttd", function () { + it("should accept powBlockParent < ttd and powBlock >= ttd", () => { const powBlock = { blockHash: "0x" + "ab".repeat(32), // genesis powBlock will have non zero parent hash diff --git a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts index fdf1a1f3bae4..b9b7f9c37b43 100644 --- a/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts +++ b/packages/fork-choice/test/unit/protoArray/getCommonAncestor.test.ts @@ -77,9 +77,13 @@ describe("getCommonAncestor", () => { for (const {nodeA, nodeB, ancestor} of testCases) { it(`${nodeA} & ${nodeB} -> ${ancestor}`, () => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // biome-ignore lint/style/noNonNullAssertion: const ancestorNode = fc.getCommonAncestor(fc.getNode(nodeA)!, fc.getNode(nodeB)!); - expect(ancestorNode && ancestorNode.blockRoot).toBe(ancestor); + if (ancestor) { + expect(ancestorNode?.blockRoot).toBe(ancestor); + } else { + expect(ancestorNode).toBeNull(); + } }); } diff --git a/packages/fork-choice/test/utils/index.ts b/packages/fork-choice/test/utils/index.ts new file mode 100644 index 000000000000..8354a8c31d56 --- /dev/null +++ b/packages/fork-choice/test/utils/index.ts @@ -0,0 +1,19 @@ +import {RootHex} from "@lodestar/types"; +import {toHex} from "@lodestar/utils"; + +const rootStateBytePrefix = 0xaa; +const rootBlockBytePrefix = 0xbb; + +export function getStateRoot(slot: number): RootHex { + const root = Buffer.alloc(32, 0x00); + root[0] = rootStateBytePrefix; + root[31] = slot; + return toHex(root); +} + +export function getBlockRoot(slot: number): RootHex { + const root = Buffer.alloc(32, 0x00); + root[0] = rootBlockBytePrefix; + root[31] = slot; + return toHex(root); +} diff --git a/packages/light-client/README.md b/packages/light-client/README.md index cd3a0265008a..0323e8fc4326 100644 --- a/packages/light-client/README.md +++ b/packages/light-client/README.md @@ -62,7 +62,7 @@ import { const config = getChainForkConfigFromNetwork("sepolia"); const logger = getConsoleLogger({logDebug: Boolean(process.env.DEBUG)}); -const api = getApiFromUrl({urls: ["https://lodestar-sepolia.chainsafe.io"]}, {config}); +const api = getApiFromUrl("https://lodestar-sepolia.chainsafe.io", "sepolia"); const lightclient = await Lightclient.initializeFromCheckpointRoot({ config, @@ -82,11 +82,11 @@ await lightclient.start(); logger.info("Lightclient synced"); lightclient.emitter.on(LightclientEvent.lightClientFinalityHeader, async (finalityUpdate) => { - logger.info(finalityUpdate); + logger.info("Received finality update", {slot: finalityUpdate.beacon.slot}); }); lightclient.emitter.on(LightclientEvent.lightClientOptimisticHeader, async (optimisticUpdate) => { - logger.info(optimisticUpdate); + logger.info("Received optimistic update", {slot: optimisticUpdate.beacon.slot}); }); ``` diff --git a/packages/light-client/package.json b/packages/light-client/package.json index 940a78ae66d5..c942ff07ed44 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.22.0", + "version": "1.23.0", "type": "module", "exports": { ".": { @@ -62,8 +62,8 @@ "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", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "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", @@ -76,18 +76,18 @@ "@chainsafe/bls": "7.1.3", "@chainsafe/blst": "^0.2.0", "@chainsafe/persistent-merkle-tree": "^0.8.0", - "@chainsafe/ssz": "^0.17.1", - "@lodestar/api": "^1.22.0", - "@lodestar/config": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/types": "^1.22.0", - "@lodestar/utils": "^1.22.0", + "@chainsafe/ssz": "^0.18.0", + "@lodestar/api": "^1.23.0", + "@lodestar/config": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0", "mitt": "^3.0.0" }, "devDependencies": { "@chainsafe/as-sha256": "^0.5.0", "@types/qs": "^6.9.7", - "fastify": "^4.27.0", + "fastify": "^5.0.0", "qs": "^6.11.1", "uint8arrays": "^5.0.1" }, diff --git a/packages/light-client/src/events.ts b/packages/light-client/src/events.ts index 5b561718e89e..7c01843d8ad0 100644 --- a/packages/light-client/src/events.ts +++ b/packages/light-client/src/events.ts @@ -15,7 +15,7 @@ export type LightclientEmitterEvents = { export type LightclientEmitter = MittEmitter; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export type MittEmitter void>> = { on(type: K, handler: T[K]): void; off(type: K, handler: T[K]): void; diff --git a/packages/light-client/src/index.ts b/packages/light-client/src/index.ts index 16ecf1adf939..098ea18adc31 100644 --- a/packages/light-client/src/index.ts +++ b/packages/light-client/src/index.ts @@ -1,5 +1,4 @@ import mitt from "mitt"; -import {fromHexString, toHexString} from "@chainsafe/ssz"; import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD} from "@lodestar/params"; import { LightClientBootstrap, @@ -13,7 +12,7 @@ import { SyncPeriod, } from "@lodestar/types"; import {createBeaconConfig, BeaconConfig, ChainForkConfig} from "@lodestar/config"; -import {isErrorAborted, sleep} from "@lodestar/utils"; +import {fromHex, isErrorAborted, sleep, toRootHex} from "@lodestar/utils"; import {getCurrentSlot, slotWithFutureTolerance, timeUntilNextEpoch} from "./utils/clock.js"; import {chunkifyInclusiveRange} from "./utils/chunkify.js"; import {LightclientEmitter, LightclientEvent} from "./events.js"; @@ -120,7 +119,7 @@ export class Lightclient { this.genesisTime = genesisData.genesisTime; this.genesisValidatorsRoot = typeof genesisData.genesisValidatorsRoot === "string" - ? fromHexString(genesisData.genesisValidatorsRoot) + ? fromHex(genesisData.genesisValidatorsRoot) : genesisData.genesisValidatorsRoot; this.config = createBeaconConfig(config, this.genesisValidatorsRoot); @@ -162,7 +161,7 @@ export class Lightclient { const {transport, checkpointRoot} = args; // Fetch bootstrap state with proof at the trusted block root - const {data: bootstrap} = await transport.getBootstrap(toHexString(checkpointRoot)); + const {data: bootstrap} = await transport.getBootstrap(toRootHex(checkpointRoot)); validateLightClientBootstrap(args.config, checkpointRoot, bootstrap); @@ -225,7 +224,6 @@ export class Lightclient { } private async runLoop(): Promise { - // eslint-disable-next-line no-constant-condition while (true) { const currentPeriod = computeSyncPeriodAtSlot(this.currentSlot); // Check if we have a sync committee for the current clock period diff --git a/packages/light-client/src/spec/index.ts b/packages/light-client/src/spec/index.ts index fc1a431129e8..0934e15b1c17 100644 --- a/packages/light-client/src/spec/index.ts +++ b/packages/light-client/src/spec/index.ts @@ -10,7 +10,7 @@ import { import {computeSyncPeriodAtSlot} from "../utils/index.js"; import {getSyncCommitteeAtPeriod, processLightClientUpdate, ProcessUpdateOpts} from "./processLightClientUpdate.js"; import {ILightClientStore, LightClientStore, LightClientStoreEvents} from "./store.js"; -import {ZERO_FINALITY_BRANCH, ZERO_HEADER, ZERO_NEXT_SYNC_COMMITTEE_BRANCH, ZERO_SYNC_COMMITTEE} from "./utils.js"; +import {ZERO_FINALITY_BRANCH, ZERO_HEADER, ZERO_SYNC_COMMITTEE, getZeroSyncCommitteeBranch} from "./utils.js"; export {isBetterUpdate, toLightClientUpdateSummary} from "./isBetterUpdate.js"; export type {LightClientUpdateSummary} from "./isBetterUpdate.js"; @@ -37,7 +37,7 @@ export class LightclientSpec { this.onUpdate(currentSlot, { attestedHeader: finalityUpdate.attestedHeader, nextSyncCommittee: ZERO_SYNC_COMMITTEE, - nextSyncCommitteeBranch: ZERO_NEXT_SYNC_COMMITTEE_BRANCH, + nextSyncCommitteeBranch: getZeroSyncCommitteeBranch(this.config.getForkName(finalityUpdate.signatureSlot)), finalizedHeader: finalityUpdate.finalizedHeader, finalityBranch: finalityUpdate.finalityBranch, syncAggregate: finalityUpdate.syncAggregate, @@ -49,7 +49,7 @@ export class LightclientSpec { this.onUpdate(currentSlot, { attestedHeader: optimisticUpdate.attestedHeader, nextSyncCommittee: ZERO_SYNC_COMMITTEE, - nextSyncCommitteeBranch: ZERO_NEXT_SYNC_COMMITTEE_BRANCH, + nextSyncCommitteeBranch: getZeroSyncCommitteeBranch(this.config.getForkName(optimisticUpdate.signatureSlot)), finalizedHeader: {beacon: ZERO_HEADER}, finalityBranch: ZERO_FINALITY_BRANCH, syncAggregate: optimisticUpdate.syncAggregate, diff --git a/packages/light-client/src/spec/isBetterUpdate.ts b/packages/light-client/src/spec/isBetterUpdate.ts index 260d54434fba..00220cd6b5dd 100644 --- a/packages/light-client/src/spec/isBetterUpdate.ts +++ b/packages/light-client/src/spec/isBetterUpdate.ts @@ -28,49 +28,49 @@ export function isBetterUpdate(newUpdate: LightClientUpdateSummary, oldUpdate: L const oldNumActiveParticipants = oldUpdate.activeParticipants; const newHasSupermajority = newNumActiveParticipants * 3 >= SYNC_COMMITTEE_SIZE * 2; const oldHasSupermajority = oldNumActiveParticipants * 3 >= SYNC_COMMITTEE_SIZE * 2; - if (newHasSupermajority != oldHasSupermajority) { + if (newHasSupermajority !== oldHasSupermajority) { return newHasSupermajority; } - if (!newHasSupermajority && newNumActiveParticipants != oldNumActiveParticipants) { + if (!newHasSupermajority && newNumActiveParticipants !== oldNumActiveParticipants) { return newNumActiveParticipants > oldNumActiveParticipants; } // Compare presence of relevant sync committee const newHasRelevantSyncCommittee = newUpdate.isSyncCommitteeUpdate && - computeSyncPeriodAtSlot(newUpdate.attestedHeaderSlot) == computeSyncPeriodAtSlot(newUpdate.signatureSlot); + computeSyncPeriodAtSlot(newUpdate.attestedHeaderSlot) === computeSyncPeriodAtSlot(newUpdate.signatureSlot); const oldHasRelevantSyncCommittee = oldUpdate.isSyncCommitteeUpdate && - computeSyncPeriodAtSlot(oldUpdate.attestedHeaderSlot) == computeSyncPeriodAtSlot(oldUpdate.signatureSlot); - if (newHasRelevantSyncCommittee != oldHasRelevantSyncCommittee) { + computeSyncPeriodAtSlot(oldUpdate.attestedHeaderSlot) === computeSyncPeriodAtSlot(oldUpdate.signatureSlot); + if (newHasRelevantSyncCommittee !== oldHasRelevantSyncCommittee) { return newHasRelevantSyncCommittee; } // Compare indication of any finality const newHasFinality = newUpdate.isFinalityUpdate; const oldHasFinality = oldUpdate.isFinalityUpdate; - if (newHasFinality != oldHasFinality) { + if (newHasFinality !== oldHasFinality) { return newHasFinality; } // Compare sync committee finality if (newHasFinality) { const newHasSyncCommitteeFinality = - computeSyncPeriodAtSlot(newUpdate.finalizedHeaderSlot) == computeSyncPeriodAtSlot(newUpdate.attestedHeaderSlot); + computeSyncPeriodAtSlot(newUpdate.finalizedHeaderSlot) === computeSyncPeriodAtSlot(newUpdate.attestedHeaderSlot); const oldHasSyncCommitteeFinality = - computeSyncPeriodAtSlot(oldUpdate.finalizedHeaderSlot) == computeSyncPeriodAtSlot(oldUpdate.attestedHeaderSlot); - if (newHasSyncCommitteeFinality != oldHasSyncCommitteeFinality) { + computeSyncPeriodAtSlot(oldUpdate.finalizedHeaderSlot) === computeSyncPeriodAtSlot(oldUpdate.attestedHeaderSlot); + if (newHasSyncCommitteeFinality !== oldHasSyncCommitteeFinality) { return newHasSyncCommitteeFinality; } } // Tiebreaker 1: Sync committee participation beyond supermajority - if (newNumActiveParticipants != oldNumActiveParticipants) { + if (newNumActiveParticipants !== oldNumActiveParticipants) { return newNumActiveParticipants > oldNumActiveParticipants; } // Tiebreaker 2: Prefer older data (fewer changes to best) - if (newUpdate.attestedHeaderSlot != oldUpdate.attestedHeaderSlot) { + if (newUpdate.attestedHeaderSlot !== oldUpdate.attestedHeaderSlot) { return newUpdate.attestedHeaderSlot < oldUpdate.attestedHeaderSlot; } return newUpdate.signatureSlot < oldUpdate.signatureSlot; diff --git a/packages/light-client/src/spec/utils.ts b/packages/light-client/src/spec/utils.ts index 3e59cb14cfa0..36bc7098fcc5 100644 --- a/packages/light-client/src/spec/utils.ts +++ b/packages/light-client/src/spec/utils.ts @@ -7,6 +7,9 @@ import { ForkName, BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH, BLOCK_BODY_EXECUTION_PAYLOAD_INDEX as EXECUTION_PAYLOAD_INDEX, + NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA, + isForkPostElectra, + FINALIZED_ROOT_DEPTH_ELECTRA, } from "@lodestar/params"; import { ssz, @@ -17,17 +20,18 @@ import { LightClientUpdate, BeaconBlockHeader, SyncCommittee, + isElectraLightClientUpdate, } from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {isValidMerkleBranch, computeEpochAtSlot, computeSyncPeriodAtSlot} from "../utils/index.js"; +import {normalizeMerkleBranch} from "../utils/normalizeMerkleBranch.js"; import {LightClientStore} from "./store.js"; export const GENESIS_SLOT = 0; export const ZERO_HASH = new Uint8Array(32); export const ZERO_PUBKEY = new Uint8Array(48); export const ZERO_SYNC_COMMITTEE = ssz.altair.SyncCommittee.defaultValue(); -export const ZERO_NEXT_SYNC_COMMITTEE_BRANCH = Array.from({length: NEXT_SYNC_COMMITTEE_DEPTH}, () => ZERO_HASH); export const ZERO_HEADER = ssz.phase0.BeaconBlockHeader.defaultValue(); export const ZERO_FINALITY_BRANCH = Array.from({length: FINALIZED_ROOT_DEPTH}, () => ZERO_HASH); /** From https://notes.ethereum.org/@vbuterin/extended_light_client_protocol#Optimistic-head-determining-function */ @@ -41,10 +45,19 @@ export function getSafetyThreshold(maxActiveParticipants: number): number { return Math.floor(maxActiveParticipants / SAFETY_THRESHOLD_FACTOR); } +export function getZeroSyncCommitteeBranch(fork: ForkName): Uint8Array[] { + const nextSyncCommitteeDepth = isForkPostElectra(fork) + ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA + : NEXT_SYNC_COMMITTEE_DEPTH; + + return Array.from({length: nextSyncCommitteeDepth}, () => ZERO_HASH); +} + export function isSyncCommitteeUpdate(update: LightClientUpdate): boolean { return ( // Fast return for when constructing full LightClientUpdate from partial updates - update.nextSyncCommitteeBranch !== ZERO_NEXT_SYNC_COMMITTEE_BRANCH && + update.nextSyncCommitteeBranch !== + getZeroSyncCommitteeBranch(isElectraLightClientUpdate(update) ? ForkName.electra : ForkName.altair) && update.nextSyncCommitteeBranch.some((branch) => !byteArrayEquals(branch, ZERO_HASH)) ); } @@ -83,17 +96,19 @@ export function upgradeLightClientHeader( const startUpgradeFromFork = Object.values(ForkName)[ForkSeq[headerFork] + 1]; switch (startUpgradeFromFork) { + // biome-ignore lint/suspicious/useDefaultSwitchClauseLast: We want default to evaluate at first to throw error early default: throw Error( `Invalid startUpgradeFromFork=${startUpgradeFromFork} for headerFork=${headerFork} in upgradeLightClientHeader to targetFork=${targetFork}` ); case ForkName.altair: + // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here case ForkName.bellatrix: // Break if no further upgradation is required else fall through if (ForkSeq[targetFork] <= ForkSeq.bellatrix) break; - // eslint-disable-next-line no-fallthrough + // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here case ForkName.capella: (upgradedHeader as LightClientHeader).execution = ssz.capella.LightClientHeader.fields.execution.defaultValue(); @@ -103,7 +118,7 @@ export function upgradeLightClientHeader( // Break if no further upgradation is required else fall through if (ForkSeq[targetFork] <= ForkSeq.capella) break; - // eslint-disable-next-line no-fallthrough + // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here case ForkName.deneb: (upgradedHeader as LightClientHeader).execution.blobGasUsed = ssz.deneb.LightClientHeader.fields.execution.fields.blobGasUsed.defaultValue(); @@ -113,14 +128,8 @@ export function upgradeLightClientHeader( // Break if no further upgradation is required else fall through if (ForkSeq[targetFork] <= ForkSeq.deneb) break; - // eslint-disable-next-line no-fallthrough case ForkName.electra: - (upgradedHeader as LightClientHeader).execution.depositRequestsRoot = - ssz.electra.LightClientHeader.fields.execution.fields.depositRequestsRoot.defaultValue(); - (upgradedHeader as LightClientHeader).execution.withdrawalRequestsRoot = - ssz.electra.LightClientHeader.fields.execution.fields.withdrawalRequestsRoot.defaultValue(); - (upgradedHeader as LightClientHeader).execution.consolidationRequestsRoot = - ssz.electra.LightClientHeader.fields.execution.fields.consolidationRequestsRoot.defaultValue(); + // No changes to LightClientHeader in Electra // Break if no further upgrades is required else fall through if (ForkSeq[targetFork] <= ForkSeq.electra) break; @@ -157,15 +166,6 @@ export function isValidLightClientHeader(config: ChainForkConfig, header: LightC } } - if (epoch < config.ELECTRA_FORK_EPOCH) { - if ( - (header as LightClientHeader).execution.depositRequestsRoot !== undefined || - (header as LightClientHeader).execution.withdrawalRequestsRoot !== undefined - ) { - return false; - } - } - return isValidMerkleBranch( config .getExecutionForkTypes(header.beacon.slot) @@ -184,6 +184,14 @@ export function upgradeLightClientUpdate( ): LightClientUpdate { update.attestedHeader = upgradeLightClientHeader(config, targetFork, update.attestedHeader); update.finalizedHeader = upgradeLightClientHeader(config, targetFork, update.finalizedHeader); + update.nextSyncCommitteeBranch = normalizeMerkleBranch( + update.nextSyncCommitteeBranch, + isForkPostElectra(targetFork) ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA : NEXT_SYNC_COMMITTEE_DEPTH + ); + update.finalityBranch = normalizeMerkleBranch( + update.finalityBranch, + isForkPostElectra(targetFork) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH + ); return update; } @@ -195,6 +203,10 @@ export function upgradeLightClientFinalityUpdate( ): LightClientFinalityUpdate { finalityUpdate.attestedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.attestedHeader); finalityUpdate.finalizedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.finalizedHeader); + finalityUpdate.finalityBranch = normalizeMerkleBranch( + finalityUpdate.finalityBranch, + isForkPostElectra(targetFork) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH + ); return finalityUpdate; } diff --git a/packages/light-client/src/spec/validateLightClientBootstrap.ts b/packages/light-client/src/spec/validateLightClientBootstrap.ts index 30540da24bd1..2eafea0791f0 100644 --- a/packages/light-client/src/spec/validateLightClientBootstrap.ts +++ b/packages/light-client/src/spec/validateLightClientBootstrap.ts @@ -2,11 +2,14 @@ import {byteArrayEquals} from "@chainsafe/ssz"; import {LightClientBootstrap, Root, ssz} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {toHex} from "@lodestar/utils"; +import {isForkPostElectra} from "@lodestar/params"; import {isValidMerkleBranch} from "../utils/verifyMerkleBranch.js"; import {isValidLightClientHeader} from "./utils.js"; const CURRENT_SYNC_COMMITTEE_INDEX = 22; const CURRENT_SYNC_COMMITTEE_DEPTH = 5; +const CURRENT_SYNC_COMMITTEE_INDEX_ELECTRA = 22; +const CURRENT_SYNC_COMMITTEE_DEPTH_ELECTRA = 6; export function validateLightClientBootstrap( config: ChainForkConfig, @@ -14,6 +17,7 @@ export function validateLightClientBootstrap( bootstrap: LightClientBootstrap ): void { const headerRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(bootstrap.header.beacon); + const fork = config.getForkName(bootstrap.header.beacon.slot); if (!isValidLightClientHeader(config, bootstrap.header)) { throw Error("Bootstrap Header is not Valid Light Client Header"); @@ -27,8 +31,8 @@ export function validateLightClientBootstrap( !isValidMerkleBranch( ssz.altair.SyncCommittee.hashTreeRoot(bootstrap.currentSyncCommittee), bootstrap.currentSyncCommitteeBranch, - CURRENT_SYNC_COMMITTEE_DEPTH, - CURRENT_SYNC_COMMITTEE_INDEX, + isForkPostElectra(fork) ? CURRENT_SYNC_COMMITTEE_DEPTH_ELECTRA : CURRENT_SYNC_COMMITTEE_DEPTH, + isForkPostElectra(fork) ? CURRENT_SYNC_COMMITTEE_INDEX_ELECTRA : CURRENT_SYNC_COMMITTEE_INDEX, bootstrap.header.beacon.stateRoot ) ) { diff --git a/packages/light-client/src/spec/validateLightClientUpdate.ts b/packages/light-client/src/spec/validateLightClientUpdate.ts index fde760da3b05..9a5ea1985f16 100644 --- a/packages/light-client/src/spec/validateLightClientUpdate.ts +++ b/packages/light-client/src/spec/validateLightClientUpdate.ts @@ -1,15 +1,19 @@ import bls from "@chainsafe/bls"; import type {PublicKey, Signature} from "@chainsafe/bls/types"; -import {LightClientUpdate, Root, ssz} from "@lodestar/types"; +import {LightClientUpdate, Root, isElectraLightClientUpdate, ssz} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import { FINALIZED_ROOT_INDEX, FINALIZED_ROOT_DEPTH, - NEXT_SYNC_COMMITTEE_INDEX, NEXT_SYNC_COMMITTEE_DEPTH, MIN_SYNC_COMMITTEE_PARTICIPANTS, DOMAIN_SYNC_COMMITTEE, GENESIS_SLOT, + NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA, + NEXT_SYNC_COMMITTEE_INDEX_ELECTRA, + NEXT_SYNC_COMMITTEE_INDEX, + FINALIZED_ROOT_DEPTH_ELECTRA, + FINALIZED_ROOT_INDEX_ELECTRA, } from "@lodestar/params"; import {getParticipantPubkeys, sumBits} from "../utils/utils.js"; import {isValidMerkleBranch} from "../utils/index.js"; @@ -78,8 +82,8 @@ export function validateLightClientUpdate( !isValidMerkleBranch( finalizedRoot, update.finalityBranch, - FINALIZED_ROOT_DEPTH, - FINALIZED_ROOT_INDEX, + isElectraLightClientUpdate(update) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH, + isElectraLightClientUpdate(update) ? FINALIZED_ROOT_INDEX_ELECTRA : FINALIZED_ROOT_INDEX, update.attestedHeader.beacon.stateRoot ) ) { @@ -98,8 +102,8 @@ export function validateLightClientUpdate( !isValidMerkleBranch( ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee), update.nextSyncCommitteeBranch, - NEXT_SYNC_COMMITTEE_DEPTH, - NEXT_SYNC_COMMITTEE_INDEX, + isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA : NEXT_SYNC_COMMITTEE_DEPTH, + isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_INDEX_ELECTRA : NEXT_SYNC_COMMITTEE_INDEX, update.attestedHeader.beacon.stateRoot ) ) { diff --git a/packages/light-client/src/utils/api.ts b/packages/light-client/src/utils/api.ts index 7947aa96dd3e..6ccb187052e1 100644 --- a/packages/light-client/src/utils/api.ts +++ b/packages/light-client/src/utils/api.ts @@ -1,13 +1,13 @@ -import {getClient, ApiClient} from "@lodestar/api"; +import {getClient, ApiClient, ApiRequestInit} from "@lodestar/api"; import {ChainForkConfig, createChainForkConfig} from "@lodestar/config"; import {NetworkName, networksChainConfig} from "@lodestar/config/networks"; -export function getApiFromUrl(url: string, network: NetworkName): ApiClient { +export function getApiFromUrl(url: string, network: NetworkName, init?: ApiRequestInit): ApiClient { if (!(network in networksChainConfig)) { throw Error(`Invalid network name "${network}". Valid options are: ${Object.keys(networksChainConfig).join()}`); } - return getClient({urls: [url]}, {config: createChainForkConfig(networksChainConfig[network])}); + return getClient({urls: [url], globalInit: init}, {config: createChainForkConfig(networksChainConfig[network])}); } export function getChainForkConfigFromNetwork(network: NetworkName): ChainForkConfig { diff --git a/packages/light-client/src/utils/clock.ts b/packages/light-client/src/utils/clock.ts index 44b74b1b601b..efe210b620d8 100644 --- a/packages/light-client/src/utils/clock.ts +++ b/packages/light-client/src/utils/clock.ts @@ -39,7 +39,7 @@ export function timeUntilNextEpoch(config: Pick const msFromGenesis = Date.now() - genesisTime * 1000; if (msFromGenesis >= 0) { return milliSecondsPerEpoch - (msFromGenesis % milliSecondsPerEpoch); - } else { - return Math.abs(msFromGenesis % milliSecondsPerEpoch); } + + return Math.abs(msFromGenesis % milliSecondsPerEpoch); } diff --git a/packages/light-client/src/utils/logger.ts b/packages/light-client/src/utils/logger.ts index afdbf7da7d3d..1f13b98bd6c6 100644 --- a/packages/light-client/src/utils/logger.ts +++ b/packages/light-client/src/utils/logger.ts @@ -1,4 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export type LogHandler = (message: string, context?: any, error?: Error) => void; export type ILcLogger = { diff --git a/packages/light-client/src/utils/normalizeMerkleBranch.ts b/packages/light-client/src/utils/normalizeMerkleBranch.ts new file mode 100644 index 000000000000..ae3309f8ff2e --- /dev/null +++ b/packages/light-client/src/utils/normalizeMerkleBranch.ts @@ -0,0 +1,15 @@ +import {ZERO_HASH} from "../spec/utils.js"; + +export const SYNC_COMMITTEES_DEPTH = 4; +export const SYNC_COMMITTEES_INDEX = 11; + +/** + * Given merkle branch ``branch``, extend its depth according to ``depth`` + * If given ``depth`` is less than the depth of ``branch``, it will return + * unmodified ``branch`` + */ +export function normalizeMerkleBranch(branch: Uint8Array[], depth: number): Uint8Array[] { + const numExtraDepth = depth - branch.length; + + return [...Array.from({length: numExtraDepth}, () => ZERO_HASH), ...branch]; +} diff --git a/packages/light-client/src/validation.ts b/packages/light-client/src/validation.ts index e7839f115153..c756d612f3e7 100644 --- a/packages/light-client/src/validation.ts +++ b/packages/light-client/src/validation.ts @@ -1,6 +1,14 @@ import bls from "@chainsafe/bls"; import type {PublicKey, Signature} from "@chainsafe/bls/types"; -import {altair, LightClientFinalityUpdate, LightClientUpdate, Root, Slot, ssz} from "@lodestar/types"; +import { + altair, + isElectraLightClientUpdate, + LightClientFinalityUpdate, + LightClientUpdate, + Root, + Slot, + ssz, +} from "@lodestar/types"; import { FINALIZED_ROOT_INDEX, FINALIZED_ROOT_DEPTH, @@ -8,6 +16,9 @@ import { NEXT_SYNC_COMMITTEE_DEPTH, MIN_SYNC_COMMITTEE_PARTICIPANTS, DOMAIN_SYNC_COMMITTEE, + NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA, + FINALIZED_ROOT_DEPTH_ELECTRA, + NEXT_SYNC_COMMITTEE_INDEX_ELECTRA, } from "@lodestar/params"; import {BeaconConfig} from "@lodestar/config"; import {isValidMerkleBranch} from "./utils/verifyMerkleBranch.js"; @@ -39,7 +50,11 @@ export function assertValidLightClientUpdate( if (isFinalized) { assertValidFinalityProof(update); } else { - assertZeroHashes(update.finalityBranch, FINALIZED_ROOT_DEPTH, "finalityBranches"); + assertZeroHashes( + update.finalityBranch, + isElectraLightClientUpdate(update) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH, + "finalityBranches" + ); } // DIFF FROM SPEC: @@ -99,8 +114,8 @@ export function assertValidSyncCommitteeProof(update: LightClientUpdate): void { !isValidMerkleBranch( ssz.altair.SyncCommittee.hashTreeRoot(update.nextSyncCommittee), update.nextSyncCommitteeBranch, - NEXT_SYNC_COMMITTEE_DEPTH, - NEXT_SYNC_COMMITTEE_INDEX, + isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA : NEXT_SYNC_COMMITTEE_DEPTH, + isElectraLightClientUpdate(update) ? NEXT_SYNC_COMMITTEE_INDEX_ELECTRA : NEXT_SYNC_COMMITTEE_INDEX, update.attestedHeader.beacon.stateRoot ) ) { diff --git a/packages/light-client/test/unit/isValidLightClientHeader.test.ts b/packages/light-client/test/unit/isValidLightClientHeader.test.ts index db27a8266103..2bbbd1250961 100644 --- a/packages/light-client/test/unit/isValidLightClientHeader.test.ts +++ b/packages/light-client/test/unit/isValidLightClientHeader.test.ts @@ -4,8 +4,7 @@ import {LightClientHeader, ssz} from "@lodestar/types"; import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {isValidLightClientHeader} from "../../src/spec/utils.js"; -describe("isValidLightClientHeader", function () { - /* eslint-disable @typescript-eslint/naming-convention */ +describe("isValidLightClientHeader", () => { const chainConfig = createChainForkConfig({ ...defaultChainConfig, ALTAIR_FORK_EPOCH: 0, @@ -88,10 +87,10 @@ describe("isValidLightClientHeader", function () { ["capella upgraded to deneb LC header", capellaUpgradedDenebHeader], ]; - testCases.forEach(([name, header]: [string, LightClientHeader]) => { - it(name, function () { + for (const [name, header] of testCases) { + it(name, () => { const isValid = isValidLightClientHeader(config, header); expect(isValid).toBe(true); }); - }); + } }); diff --git a/packages/light-client/test/unit/sync.node.test.ts b/packages/light-client/test/unit/sync.node.test.ts index bcd1d0f25d0b..52afd05760d7 100644 --- a/packages/light-client/test/unit/sync.node.test.ts +++ b/packages/light-client/test/unit/sync.node.test.ts @@ -45,7 +45,6 @@ describe("sync", () => { const targetSlot = firstHeadSlot + slotsIntoPeriod; // Genesis data such that targetSlot is at the current clock slot - // eslint-disable-next-line @typescript-eslint/naming-convention const chainConfig: ChainConfig = {...chainConfigDef, SECONDS_PER_SLOT, ALTAIR_FORK_EPOCH}; const genesisTime = Math.floor(Date.now() / 1000) - chainConfig.SECONDS_PER_SLOT * targetSlot; const genesisValidatorsRoot = Buffer.alloc(32, 0xaa); diff --git a/packages/light-client/test/unit/syncInMemory.test.ts b/packages/light-client/test/unit/syncInMemory.test.ts index 770827e86655..6118a1b4e61a 100644 --- a/packages/light-client/test/unit/syncInMemory.test.ts +++ b/packages/light-client/test/unit/syncInMemory.test.ts @@ -22,7 +22,7 @@ function getSyncCommittee( return syncCommitteeKeys; } -describe("syncInMemory", function () { +describe("syncInMemory", () => { // In browser test this process is taking more time than default 2000ms vi.setConfig({testTimeout: 10000}); @@ -39,9 +39,7 @@ describe("syncInMemory", function () { expect(sk.toPublicKey().toHex()).toBe( "0xaa1a1c26055a329817a5759d877a2795f9499b97d6056edde0eea39512f24e8bc874b4471f0501127abb1ea0d9f68ac1" ); - }); - beforeAll(() => { // Create a state that has as nextSyncCommittee the committee 2 const finalizedBlockSlot = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD + 1; const headerBlockSlot = finalizedBlockSlot + 1; diff --git a/packages/light-client/test/unit/utils.test.ts b/packages/light-client/test/unit/utils.test.ts index 91bfab113431..9913c6c462a4 100644 --- a/packages/light-client/test/unit/utils.test.ts +++ b/packages/light-client/test/unit/utils.test.ts @@ -1,6 +1,8 @@ import {describe, it, expect} from "vitest"; import {isValidMerkleBranch} from "../../src/utils/verifyMerkleBranch.js"; import {computeMerkleBranch} from "../utils/utils.js"; +import {normalizeMerkleBranch} from "../../src/utils/normalizeMerkleBranch.js"; +import {ZERO_HASH} from "../../src/spec/utils.js"; describe("utils", () => { it("constructMerkleBranch", () => { @@ -11,4 +13,18 @@ describe("utils", () => { expect(isValidMerkleBranch(leaf, proof, depth, index, root)).toBe(true); }); + it("normalizeMerkleBranch", () => { + const branch: Uint8Array[] = []; + const branchDepth = 5; + const newDepth = 7; + + for (let i = 0; i < branchDepth; i++) { + branch.push(new Uint8Array(Array.from({length: 32}, () => i))); + } + + const normalizedBranch = normalizeMerkleBranch(branch, newDepth); + const expectedNormalizedBranch = [ZERO_HASH, ZERO_HASH, ...branch]; + + expect(normalizedBranch).toEqual(expectedNormalizedBranch); + }); }); diff --git a/packages/light-client/test/unit/validation.test.ts b/packages/light-client/test/unit/validation.test.ts index 61442fb4bf8c..6ed3b714a690 100644 --- a/packages/light-client/test/unit/validation.test.ts +++ b/packages/light-client/test/unit/validation.test.ts @@ -15,7 +15,7 @@ import {assertValidLightClientUpdate} from "../../src/validation.js"; import {LightClientSnapshotFast, SyncCommitteeFast} from "../../src/types.js"; import {defaultBeaconBlockHeader, getSyncAggregateSigningRoot, signAndAggregate} from "../utils/utils.js"; -describe("validation", function () { +describe("validation", () => { // In browser test this process is taking more time than default 2000ms // specially on the CI vi.setConfig({testTimeout: 15000}); @@ -26,7 +26,7 @@ describe("validation", function () { let update: altair.LightClientUpdate; let snapshot: LightClientSnapshotFast; - beforeAll(function () { + beforeAll(() => { // Update slot must > snapshot slot // attestedHeaderSlot must == updateHeaderSlot + 1 const snapshotHeaderSlot = 1; diff --git a/packages/logger/package.json b/packages/logger/package.json index 196fb306a8b0..b9e9beef39be 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.22.0", + "version": "1.23.0", "type": "module", "exports": { ".": { @@ -53,8 +53,8 @@ "build:release": "yarn clean && yarn build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit && yarn test:e2e", "test:unit": "vitest --run --dir test/unit/", "test:browsers": "yarn test:browsers:chrome && yarn test:browsers:firefox && yarn test:browsers:electron", @@ -66,14 +66,14 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@lodestar/utils": "^1.22.0", + "@lodestar/utils": "^1.23.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.22.0", + "@lodestar/test-utils": "^1.23.0", "@types/triple-beam": "^1.3.2", "triple-beam": "^1.3.0" }, diff --git a/packages/logger/src/browser.ts b/packages/logger/src/browser.ts index fc91fcfcd2a3..7f4972111459 100644 --- a/packages/logger/src/browser.ts +++ b/packages/logger/src/browser.ts @@ -57,7 +57,7 @@ class BrowserConsole extends Transport { constructor(opts: winston.transport.TransportStreamOptions | undefined) { super(opts); - this.level = opts?.level && this.levels.hasOwnProperty(opts.level) ? opts.level : "info"; + this.level = opts?.level && Object.prototype.hasOwnProperty.call(this.levels, opts.level) ? opts.level : "info"; } log(info: WinstonLogInfo, callback: () => void): void { @@ -70,9 +70,7 @@ class BrowserConsole extends Transport { const message = info[MESSAGE]; if (val <= this.levels[this.level as LogLevel]) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, no-console console[mappedMethod](message); } diff --git a/packages/logger/src/env.ts b/packages/logger/src/env.ts index 201d91c69069..e81ae9613e15 100644 --- a/packages/logger/src/env.ts +++ b/packages/logger/src/env.ts @@ -6,20 +6,18 @@ import {LogFormat, TimestampFormat} from "./interface.js"; export function getEnvLogLevel(): LogLevel | null { if (process == null) return null; - if (process.env["LOG_LEVEL"]) return process.env["LOG_LEVEL"] as LogLevel; - if (process.env["DEBUG"]) return LogLevel.debug; - if (process.env["VERBOSE"]) return LogLevel.verbose; + if (process.env.LOG_LEVEL) return process.env.LOG_LEVEL as LogLevel; + if (process.env.DEBUG) return LogLevel.debug; + if (process.env.VERBOSE) return LogLevel.verbose; return null; } export function getEnvLogger(opts?: Partial): Logger { const level = opts?.level ?? getEnvLogLevel(); - const format = (opts?.format ?? process.env["LOG_FORMAT"]) as LogFormat; + const format = (opts?.format ?? process.env.LOG_FORMAT) as LogFormat; const timestampFormat = opts?.timestampFormat ?? - ((process.env["LOG_TIMESTAMP_FORMAT"] - ? {format: process.env["LOG_TIMESTAMP_FORMAT"]} - : undefined) as TimestampFormat); + ((process.env.LOG_TIMESTAMP_FORMAT ? {format: process.env.LOG_TIMESTAMP_FORMAT} : undefined) as TimestampFormat); if (level != null) { return getBrowserLogger({...opts, level, format, timestampFormat}); diff --git a/packages/logger/src/interface.ts b/packages/logger/src/interface.ts index 8f270b0dc4ee..e3ba8483a458 100644 --- a/packages/logger/src/interface.ts +++ b/packages/logger/src/interface.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-extraneous-dependencies import {LEVEL, MESSAGE} from "triple-beam"; import {LogLevel, Logger, LogHandler, LogData} from "@lodestar/utils"; @@ -15,7 +14,6 @@ export const logLevelNum: {[K in LogLevel]: number} = { [LogLevel.trace]: 5, }; -// eslint-disable-next-line @typescript-eslint/naming-convention export const LogLevels = Object.values(LogLevel); export type LogFormat = "human" | "json"; diff --git a/packages/logger/src/node.ts b/packages/logger/src/node.ts index 60ca87e0367c..fcd9c535dd9e 100644 --- a/packages/logger/src/node.ts +++ b/packages/logger/src/node.ts @@ -128,7 +128,7 @@ export class WinstonLoggerNode extends WinstonLogger implements LoggerNode { } static fromOpts(opts: LoggerNodeOpts, transports: winston.transport[]): WinstonLoggerNode { - return new WinstonLoggerNode(this.createWinstonInstance(opts, transports), opts); + return new WinstonLoggerNode(WinstonLoggerNode.createWinstonInstance(opts, transports), opts); } static fromNewTransports(opts: LoggerNodeOpts): WinstonLoggerNode { diff --git a/packages/logger/src/utils/consoleTransport.ts b/packages/logger/src/utils/consoleTransport.ts index 41a1084b4b83..6bfd62b2ecd8 100644 --- a/packages/logger/src/utils/consoleTransport.ts +++ b/packages/logger/src/utils/consoleTransport.ts @@ -26,6 +26,7 @@ export class ConsoleDynamicLevel extends transports.Console { return this.levelByModule.delete(module); } + // biome-ignore lint/correctness/noUndeclaredVariables: BufferEncoding is not been identified by the biomejs _write(info: WinstonLogInfo, enc: BufferEncoding, callback: (error?: Error | null | undefined) => void): void { const moduleLevel = this.levelByModule.get(info.module) ?? this.defaultLevel; diff --git a/packages/logger/src/utils/format.ts b/packages/logger/src/utils/format.ts index 651dc56ce687..4e657c0040af 100644 --- a/packages/logger/src/utils/format.ts +++ b/packages/logger/src/utils/format.ts @@ -21,8 +21,8 @@ export function getFormat(opts: LoggerOptions): Format { switch (opts.format) { case "json": return jsonLogFormat(opts); - case "human": + return humanReadableLogFormat(opts); default: return humanReadableLogFormat(opts); } @@ -49,6 +49,7 @@ function formatTimestamp(opts: LoggerOptions): Format { }; case TimestampFormatCode.DateRegular: + return format.timestamp({format: "MMM-DD HH:mm:ss.SSS"}); default: return format.timestamp({format: "MMM-DD HH:mm:ss.SSS"}); } @@ -70,7 +71,8 @@ function jsonLogFormat(opts: LoggerOptions): Format { /** * Winston template function print a human readable string given a log object */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any + +// biome-ignore lint/suspicious/noExplicitAny: function humanReadableTemplateFn(_info: {[key: string]: any; level: string; message: string}): string { const info = _info as WinstonInfoArg; diff --git a/packages/logger/src/utils/json.ts b/packages/logger/src/utils/json.ts index f6f2d85487c7..bab060cc8187 100644 --- a/packages/logger/src/utils/json.ts +++ b/packages/logger/src/utils/json.ts @@ -1,4 +1,4 @@ -import {LodestarError, mapValues, toHexString} from "@lodestar/utils"; +import {LodestarError, mapValues, toHex} from "@lodestar/utils"; const MAX_DEPTH = 0; @@ -29,7 +29,7 @@ export function logCtxToJson(arg: unknown, depth = 0, fromError = false): LogDat if (arg === null) return "null"; if (arg instanceof Uint8Array) { - return toHexString(arg); + return toHex(arg); } // For any type that may include recursiveness break early at the first level @@ -44,10 +44,9 @@ export function logCtxToJson(arg: unknown, depth = 0, fromError = false): LogDat if (arg instanceof LodestarError) { if (fromError) { return "[LodestarErrorCircular]"; - } else { - // Allow one extra depth level for LodestarError - metadata = logCtxToJson(arg.getMetadata(), depth - 1, true) as Record; } + // Allow one extra depth level for LodestarError + metadata = logCtxToJson(arg.getMetadata(), depth - 1, true) as Record; } else { metadata = {message: arg.message}; } @@ -90,7 +89,7 @@ export function logCtxToString(arg: unknown, depth = 0, fromError = false): stri if (arg === null) return "null"; if (arg instanceof Uint8Array) { - return toHexString(arg); + return toHex(arg); } // For any type that may include recursiveness break early at the first level @@ -105,10 +104,9 @@ export function logCtxToString(arg: unknown, depth = 0, fromError = false): stri if (arg instanceof LodestarError) { if (fromError) { return "[LodestarErrorCircular]"; - } else { - // Allow one extra depth level for LodestarError - metadata = logCtxToString(arg.getMetadata(), depth - 1, true); } + // Allow one extra depth level for LodestarError + metadata = logCtxToString(arg.getMetadata(), depth - 1, true); } else { metadata = arg.message; } @@ -127,6 +125,7 @@ export function logCtxToString(arg: unknown, depth = 0, fromError = false): stri case "string": case "undefined": case "boolean": + return String(arg); default: return String(arg); } diff --git a/packages/logger/src/winston.ts b/packages/logger/src/winston.ts index 4e6fbbecd6b2..b886894e6aba 100644 --- a/packages/logger/src/winston.ts +++ b/packages/logger/src/winston.ts @@ -42,7 +42,7 @@ export class WinstonLogger implements Logger { constructor(protected readonly winston: Winston) {} static fromOpts(options: Partial = {}, transports?: winston.transport[]): WinstonLogger { - return new WinstonLogger(this.createWinstonInstance(options, transports)); + return new WinstonLogger(WinstonLogger.createWinstonInstance(options, transports)); } static createWinstonInstance(options: Partial = {}, transports?: winston.transport[]): Winston { diff --git a/packages/logger/test/e2e/logger/workerLogger.js b/packages/logger/test/e2e/logger/workerLogger.js index 9608336c433f..83d1c857631f 100644 --- a/packages/logger/test/e2e/logger/workerLogger.js +++ b/packages/logger/test/e2e/logger/workerLogger.js @@ -9,7 +9,6 @@ if (!parentPort) throw Error("parentPort must be defined"); const file = fs.createWriteStream(workerData.logFilepath, {flags: "a"}); parentPort.on("message", (data) => { - // eslint-disable-next-line no-console console.log(data); file.write(data); }); diff --git a/packages/logger/test/e2e/logger/workerLogs.test.ts b/packages/logger/test/e2e/logger/workerLogs.test.ts index 3c81cbf92c57..01ede8f6e4ec 100644 --- a/packages/logger/test/e2e/logger/workerLogs.test.ts +++ b/packages/logger/test/e2e/logger/workerLogs.test.ts @@ -7,10 +7,9 @@ import {LoggerWorker, getLoggerWorker} from "./workerLoggerHandler.js"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); -describe("worker logs", function () { +describe("worker logs", () => { vi.setConfig({testTimeout: 60_000}); const logFilepath = path.join(__dirname, "../../../test-logs/test_worker_logs.log"); diff --git a/packages/logger/test/unit/node.node.test.ts b/packages/logger/test/unit/node.node.test.ts index 12782fa49af8..b7c882a1e3bd 100644 --- a/packages/logger/test/unit/node.node.test.ts +++ b/packages/logger/test/unit/node.node.test.ts @@ -6,7 +6,7 @@ import {formatsTestCases} from "../fixtures/loggerFormats.js"; // Node.js maps `process.stdout` to `console._stdout`. // spy does not work on `process.stdout` directly. -// eslint-disable-next-line @typescript-eslint/naming-convention +// biome-ignore lint/style/useNamingConvention: Need property name _stdout for testing type TestConsole = typeof console & {_stdout: {write: Mock}}; describe("node logger", () => { diff --git a/packages/logger/test/unit/utils/json.test.ts b/packages/logger/test/unit/utils/json.test.ts index 912f15fa958b..7ca5604edf00 100644 --- a/packages/logger/test/unit/utils/json.test.ts +++ b/packages/logger/test/unit/utils/json.test.ts @@ -24,8 +24,10 @@ describe("Json helper", () => { {id: "symbol", arg: Symbol("foo"), json: "Symbol(foo)"}, // Functions + // biome-ignore lint/complexity/useArrowFunction: We need a function for the this test {id: "function", arg: function () {}, json: "function() {\n }"}, {id: "arrow function", arg: () => {}, json: "() => {\n }"}, + // biome-ignore lint/complexity/useArrowFunction: We need a function for the this test {id: "async function", arg: async function () {}, json: "async function() {\n }"}, {id: "async arrow function", arg: async () => {}, json: "async () => {\n }"}, @@ -113,7 +115,6 @@ describe("Json helper", () => { // Circular references () => { const circularReference: any = {}; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access circularReference.myself = circularReference; return { id: "circular reference", @@ -161,8 +162,7 @@ describe("Json helper", () => { // Objects {id: "object of basic types", json: {a: 1, b: "a", c: root}, output: `a=1, b=a, c=${rootHex}`}, - // eslint-disable-next-line quotes - {id: "object of objects", json: {a: {b: 1}}, output: `a=[object]`}, + {id: "object of objects", json: {a: {b: 1}}, output: "a=[object]"}, { id: "error metadata", json: { @@ -175,7 +175,6 @@ describe("Json helper", () => { // Circular references () => { const circularReference: any = {}; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access circularReference.myself = circularReference; return { id: "circular reference", diff --git a/packages/logger/test/unit/winston.node.test.ts b/packages/logger/test/unit/winston.node.test.ts index 8ef49da4e02d..e4cfca2b041a 100644 --- a/packages/logger/test/unit/winston.node.test.ts +++ b/packages/logger/test/unit/winston.node.test.ts @@ -8,7 +8,7 @@ import {readFileWhenExists} from "../utils/files.js"; // Node.js maps `process.stdout` to `console._stdout`. // spy does not work on `process.stdout` directly. -// eslint-disable-next-line @typescript-eslint/naming-convention +// biome-ignore lint/style/useNamingConvention: Need property name _stdout for testing type TestConsole = typeof console & {_stdout: {write: Mock}}; describe("winston logger", () => { diff --git a/packages/params/package.json b/packages/params/package.json index c281f97a41ec..ce78e826ef18 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/params", - "version": "1.22.0", + "version": "1.23.0", "description": "Chain parameters required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -50,8 +50,8 @@ "build:watch": "yarn run build --watch", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "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", diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index e7fd5b976336..544113e3f8e1 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -96,7 +96,7 @@ export const { MAX_EFFECTIVE_BALANCE_ELECTRA, MIN_ACTIVATION_BALANCE, - PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_DEPOSITS_LIMIT, PENDING_PARTIAL_WITHDRAWALS_LIMIT, PENDING_CONSOLIDATIONS_LIMIT, MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA, @@ -107,6 +107,7 @@ export const { MAX_ATTESTER_SLASHINGS_ELECTRA, MAX_ATTESTATIONS_ELECTRA, MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP, + MAX_PENDING_DEPOSITS_PER_EPOCH, WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA, } = activePreset; @@ -124,6 +125,8 @@ export const FAR_FUTURE_EPOCH = Infinity; export const BASE_REWARDS_PER_EPOCH = 4; export const DEPOSIT_CONTRACT_TREE_DEPTH = 2 ** 5; // 32 export const JUSTIFICATION_BITS_LENGTH = 4; +export const ZERO_HASH = Buffer.alloc(32, 0); +export const ZERO_HASH_HEX = "0x" + "00".repeat(32); // Withdrawal prefixes // Since the prefixes are just 1 byte, we define and use them as number @@ -144,7 +147,6 @@ export const DOMAIN_SYNC_COMMITTEE = Uint8Array.from([7, 0, 0, 0]); export const DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF = Uint8Array.from([8, 0, 0, 0]); export const DOMAIN_CONTRIBUTION_AND_PROOF = Uint8Array.from([9, 0, 0, 0]); export const DOMAIN_BLS_TO_EXECUTION_CHANGE = Uint8Array.from([10, 0, 0, 0]); -export const DOMAIN_CONSOLIDATION = Uint8Array.from([11, 0, 0, 0]); // Application specific domains @@ -264,7 +266,9 @@ export const BLOBSIDECAR_FIXED_SIZE = ACTIVE_PRESET === PresetName.minimal ? 131 // Electra Misc export const UNSET_DEPOSIT_REQUESTS_START_INDEX = 2n ** 64n - 1n; export const FULL_EXIT_REQUEST_AMOUNT = 0; +export const FINALIZED_ROOT_GINDEX_ELECTRA = 169; +export const FINALIZED_ROOT_DEPTH_ELECTRA = 7; +export const FINALIZED_ROOT_INDEX_ELECTRA = 41; export const NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA = 87; export const NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA = 6; -export const FINALIZED_ROOT_DEPTH_ELECTRA = 7; -export const FINALIZED_ROOT_INDEX_ELECTRA = 169; +export const NEXT_SYNC_COMMITTEE_INDEX_ELECTRA = 23; diff --git a/packages/params/src/json.ts b/packages/params/src/json.ts index 8d475e680f4d..9fe49a920424 100644 --- a/packages/params/src/json.ts +++ b/packages/params/src/json.ts @@ -50,7 +50,7 @@ function deserializePresetValue(valueStr: unknown, keyName: string): number { const value = parseInt(valueStr, 10); - if (isNaN(value)) { + if (Number.isNaN(value)) { throw Error(`Invalid ${keyName} value ${valueStr} expected number`); } diff --git a/packages/params/src/presets/gnosis.ts b/packages/params/src/presets/gnosis.ts index 412c38a6eb82..5a4a2eac4113 100644 --- a/packages/params/src/presets/gnosis.ts +++ b/packages/params/src/presets/gnosis.ts @@ -4,7 +4,6 @@ import {mainnetPreset} from "./mainnet.js"; // Gnosis preset // https://github.com/gnosischain/specs/tree/master/consensus/preset/gnosis -/* eslint-disable @typescript-eslint/naming-convention */ export const gnosisPreset: BeaconPreset = { ...mainnetPreset, diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index a4a88aac1f58..9a03001375f2 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -3,7 +3,6 @@ import {BeaconPreset} from "../types.js"; // Mainnet preset // https://github.com/ethereum/consensus-specs/tree/dev/presets/mainnet -/* eslint-disable @typescript-eslint/naming-convention */ export const mainnetPreset: BeaconPreset = { // Misc // --------------------------------------------------------------- @@ -125,12 +124,12 @@ export const mainnetPreset: BeaconPreset = { MAX_ATTESTER_SLASHINGS_ELECTRA: 1, MAX_ATTESTATIONS_ELECTRA: 8, MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8, + MAX_PENDING_DEPOSITS_PER_EPOCH: 16, // 2**11 * 10**9 (= 2,048,000,000,000) Gwei MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000, - // 2**16 (= 65536) MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096, MIN_ACTIVATION_BALANCE: 32000000000, - PENDING_BALANCE_DEPOSITS_LIMIT: 134217728, + PENDING_DEPOSITS_LIMIT: 134217728, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728, PENDING_CONSOLIDATIONS_LIMIT: 262144, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1, diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 97eff53cf013..6edd7e2858f1 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -3,7 +3,6 @@ import {BeaconPreset} from "../types.js"; // Minimal preset // https://github.com/ethereum/consensus-specs/tree/dev/presets/minimal -/* eslint-disable @typescript-eslint/naming-convention */ export const minimalPreset: BeaconPreset = { // Misc // --------------------------------------------------------------- @@ -125,13 +124,13 @@ export const minimalPreset: BeaconPreset = { MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 2, MAX_ATTESTER_SLASHINGS_ELECTRA: 1, MAX_ATTESTATIONS_ELECTRA: 8, - MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 1, + MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2, + MAX_PENDING_DEPOSITS_PER_EPOCH: 16, // 2**11 * 10**9 (= 2,048,000,000,000) Gwei MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000, - // 2**16 (= 65536) MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096, MIN_ACTIVATION_BALANCE: 32000000000, - PENDING_BALANCE_DEPOSITS_LIMIT: 134217728, + PENDING_DEPOSITS_LIMIT: 134217728, PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64, PENDING_CONSOLIDATIONS_LIMIT: 64, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 1, diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index e867b4a3cf71..5e17adaace12 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - /** * Compile-time chain configuration */ @@ -89,10 +87,11 @@ export type BeaconPreset = { MAX_ATTESTER_SLASHINGS_ELECTRA: number; MAX_ATTESTATIONS_ELECTRA: number; MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: number; + MAX_PENDING_DEPOSITS_PER_EPOCH: number; MAX_EFFECTIVE_BALANCE_ELECTRA: number; MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: number; MIN_ACTIVATION_BALANCE: number; - PENDING_BALANCE_DEPOSITS_LIMIT: number; + PENDING_DEPOSITS_LIMIT: number; PENDING_PARTIAL_WITHDRAWALS_LIMIT: number; PENDING_CONSOLIDATIONS_LIMIT: number; MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: number; @@ -189,10 +188,11 @@ export const beaconPresetTypes: BeaconPresetTypes = { MAX_ATTESTER_SLASHINGS_ELECTRA: "number", MAX_ATTESTATIONS_ELECTRA: "number", MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: "number", + MAX_PENDING_DEPOSITS_PER_EPOCH: "number", MAX_EFFECTIVE_BALANCE_ELECTRA: "number", MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: "number", MIN_ACTIVATION_BALANCE: "number", - PENDING_BALANCE_DEPOSITS_LIMIT: "number", + PENDING_DEPOSITS_LIMIT: "number", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "number", PENDING_CONSOLIDATIONS_LIMIT: "number", MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "number", diff --git a/packages/params/test/e2e/ensure-config-is-synced.test.ts b/packages/params/test/e2e/ensure-config-is-synced.test.ts index 38168fa02bae..c54b0d4d44f8 100644 --- a/packages/params/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/params/test/e2e/ensure-config-is-synced.test.ts @@ -8,29 +8,45 @@ import {loadConfigYaml} from "../yaml.js"; // Not e2e, but slow. Run with e2e tests /** https://github.com/ethereum/consensus-specs/releases */ -const specConfigCommit = "v1.5.0-alpha.3"; +const specConfigCommit = "v1.5.0-alpha.8"; +/** + * Fields that we filter from local config when doing comparison. + * Ideally this should be empty as it is not spec compliant + * For `MAX_BLOBS_PER_BLOCK`, see https://github.com/ChainSafe/lodestar/issues/7172 + */ +const ignoredLocalPresetFields: (keyof BeaconPreset)[] = ["MAX_BLOBS_PER_BLOCK"]; -describe("Ensure config is synced", function () { +describe("Ensure config is synced", () => { vi.setConfig({testTimeout: 60 * 1000}); - it("mainnet", async function () { + it("mainnet", async () => { const remotePreset = await downloadRemoteConfig("mainnet", specConfigCommit); assertCorrectPreset({...mainnetPreset}, remotePreset); }); - it("minimal", async function () { + it("minimal", async () => { const remotePreset = await downloadRemoteConfig("minimal", specConfigCommit); assertCorrectPreset({...minimalPreset}, remotePreset); }); }); function assertCorrectPreset(localPreset: BeaconPreset, remotePreset: BeaconPreset): void { + const filteredLocalPreset: Partial = Object.keys(localPreset) + .filter((key) => !ignoredLocalPresetFields.includes(key as keyof BeaconPreset)) + .reduce( + (acc, key) => { + acc[key as keyof BeaconPreset] = localPreset[key as keyof BeaconPreset]; + return acc; + }, + {} as Partial + ); + // Check each key for better debuggability for (const key of Object.keys(remotePreset) as (keyof BeaconPreset)[]) { - expect(localPreset[key]).toBe(remotePreset[key]); + expect(filteredLocalPreset[key]).toBe(remotePreset[key]); } - expect(localPreset).toEqual(remotePreset); + expect(filteredLocalPreset).toEqual(remotePreset); } async function downloadRemoteConfig(preset: "mainnet" | "minimal", commit: string): Promise { @@ -44,7 +60,6 @@ async function downloadRemoteConfig(preset: "mainnet" | "minimal", commit: strin ); // Merge all the fetched yamls for the different forks - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const beaconPresetRaw: Record = Object.assign( ...(downloadedParams as unknown as [input: Record]) ); diff --git a/packages/params/test/e2e/overridePreset.test.ts b/packages/params/test/e2e/overridePreset.test.ts index 24995cbaa042..df7afbbf84da 100644 --- a/packages/params/test/e2e/overridePreset.test.ts +++ b/packages/params/test/e2e/overridePreset.test.ts @@ -13,10 +13,9 @@ const exec = util.promisify(child.exec); // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); -describe("Override preset", function () { +describe("Override preset", () => { // Allow time for ts-node to compile Typescript source vi.setConfig({testTimeout: 30_000}); diff --git a/packages/params/test/e2e/overridePresetError.ts b/packages/params/test/e2e/overridePresetError.ts index d5a665bece1f..869f521d33b5 100644 --- a/packages/params/test/e2e/overridePresetError.ts +++ b/packages/params/test/e2e/overridePresetError.ts @@ -5,5 +5,4 @@ import "../../lib/index.js"; import {setActivePreset, PresetName} from "../../lib/setPreset.js"; // This line should throw -// eslint-disable-next-line @typescript-eslint/naming-convention setActivePreset(PresetName.minimal, {SLOTS_PER_EPOCH: 2}); diff --git a/packages/params/test/e2e/overridePresetOk.ts b/packages/params/test/e2e/overridePresetOk.ts index 7373ecee7fff..8887155b5400 100644 --- a/packages/params/test/e2e/overridePresetOk.ts +++ b/packages/params/test/e2e/overridePresetOk.ts @@ -6,7 +6,6 @@ import assert from "node:assert"; // 1. Import from @lodestar/params/setPreset only import {setActivePreset, PresetName} from "../../src/setPreset.js"; -// eslint-disable-next-line @typescript-eslint/naming-convention setActivePreset(PresetName.minimal, {SLOTS_PER_EPOCH: 2}); // 2. Import from any other @lodestar/params paths diff --git a/packages/params/test/e2e/setPreset.test.ts b/packages/params/test/e2e/setPreset.test.ts index 844239d56bab..2108a4f23342 100644 --- a/packages/params/test/e2e/setPreset.test.ts +++ b/packages/params/test/e2e/setPreset.test.ts @@ -13,10 +13,9 @@ const exec = util.promisify(child.exec); // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); -describe("setPreset", function () { +describe("setPreset", () => { // Allow time for ts-node to compile Typescript source vi.setConfig({testTimeout: 30_000}); diff --git a/packages/params/test/yaml.ts b/packages/params/test/yaml.ts index 162d48bc4084..f56016a4dbc0 100644 --- a/packages/params/test/yaml.ts +++ b/packages/params/test/yaml.ts @@ -8,10 +8,7 @@ export const schema = FAILSAFE_SCHEMA.extend({ implicit: [ new Type("tag:yaml.org,2002:str", { kind: "scalar", - construct: function (data) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return data !== null ? data : ""; - }, + construct: (data) => (data !== null ? data : ""), }), ], }); diff --git a/packages/prover/package.json b/packages/prover/package.json index 6a31b5b1baa0..eee42f4226c2 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.22.0", + "version": "1.23.0", "type": "module", "exports": { ".": { @@ -48,8 +48,8 @@ "build:release": "yarn clean && yarn run build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit && yarn test:e2e", "test:unit": "vitest --run --dir test/unit/", "test:browsers": "yarn test:browsers:chrome && yarn test:browsers:firefox && yarn test:browsers:electron", @@ -69,13 +69,13 @@ "@ethereumjs/tx": "^4.1.2", "@ethereumjs/util": "^8.0.6", "@ethereumjs/vm": "^6.4.2", - "@lodestar/api": "^1.22.0", - "@lodestar/config": "^1.22.0", - "@lodestar/light-client": "^1.22.0", - "@lodestar/logger": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/types": "^1.22.0", - "@lodestar/utils": "^1.22.0", + "@lodestar/api": "^1.23.0", + "@lodestar/config": "^1.23.0", + "@lodestar/light-client": "^1.23.0", + "@lodestar/logger": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.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.22.0", + "@lodestar/test-utils": "^1.23.0", "@types/http-proxy": "^1.17.10", "@types/yargs": "^17.0.24", "axios": "^1.3.4", diff --git a/packages/prover/scripts/generate_fixtures.ts b/packages/prover/scripts/generate_fixtures.ts index 70ab31fed5c1..b5d1d18d89f1 100644 --- a/packages/prover/scripts/generate_fixtures.ts +++ b/packages/prover/scripts/generate_fixtures.ts @@ -2,9 +2,7 @@ import {writeFile, mkdir} from "node:fs/promises"; import path from "node:path"; import url from "node:url"; -// eslint-disable-next-line import/no-extraneous-dependencies import axios from "axios"; -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); type JSONRequest = {method: string; params: unknown[]}; @@ -95,17 +93,14 @@ async function generateFixture(label: string, generator: Generator, network: NET } const beacon = { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - executionPayload: ((await rawBeacon(network, `eth/v2/beacon/blocks/${slot}`)) as any).data.message.body + executionPayload: ((await rawBeacon(network, `eth/v2/beacon/blocks/${slot}`)) as any).data.message.body .execution_payload, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - headers: ((await rawBeacon(network, `eth/v1/beacon/headers/${slot}`)) as any).data, + headers: ((await rawBeacon(network, `eth/v1/beacon/headers/${slot}`)) as any).data, }; const payloadBlock = await getBlockByHash( network, - // eslint-disable-next-line @typescript-eslint/naming-convention - (beacon.executionPayload as {block_hash: string}).block_hash, + (beacon.executionPayload as {block_hash: string}).block_hash, true ); diff --git a/packages/prover/src/cli/applyPreset.ts b/packages/prover/src/cli/applyPreset.ts index 158e05243ec7..f0c3d83c7751 100644 --- a/packages/prover/src/cli/applyPreset.ts +++ b/packages/prover/src/cli/applyPreset.ts @@ -1,8 +1,6 @@ // MUST import this file first before anything and not import any Lodestar code. -// eslint-disable-next-line no-restricted-imports, import/no-extraneous-dependencies import {hasher} from "@chainsafe/persistent-merkle-tree/lib/hasher/as-sha256.js"; -// eslint-disable-next-line no-restricted-imports, import/no-extraneous-dependencies import {setHasher} from "@chainsafe/persistent-merkle-tree/lib/hasher/index.js"; // without setting this first, persistent-merkle-tree will use noble instead @@ -45,7 +43,6 @@ else if (network) { if (network === "dev") { process.env.LODESTAR_PRESET = "minimal"; // "c-kzg" has hardcoded the mainnet value, do not use presets - // eslint-disable-next-line @typescript-eslint/naming-convention setActivePreset(PresetName.minimal, {FIELD_ELEMENTS_PER_BLOB: 4096}); } else if (network === "gnosis" || network === "chiado") { process.env.LODESTAR_PRESET = "gnosis"; @@ -85,6 +82,3 @@ function valueOfArg(argName: string): string | null { return null; } - -// Add empty export to make this a module -export {}; diff --git a/packages/prover/src/cli/cmds/start/options.ts b/packages/prover/src/cli/cmds/start/options.ts index f63ee974be44..5366efd96f6e 100644 --- a/packages/prover/src/cli/cmds/start/options.ts +++ b/packages/prover/src/cli/cmds/start/options.ts @@ -57,7 +57,7 @@ export const startOptions: CliCommandOptions = { string: true, coerce: (urls: string[]): string[] => // Parse ["url1,url2"] to ["url1", "url2"] - urls.map((item) => item.split(",")).flat(), + urls.flatMap((item) => item.split(",")), demandOption: true, group: "beacon", }, diff --git a/packages/prover/src/cli/index.ts b/packages/prover/src/cli/index.ts index 845831b32cb0..5bd071642c96 100644 --- a/packages/prover/src/cli/index.ts +++ b/packages/prover/src/cli/index.ts @@ -14,7 +14,7 @@ void prover // Show command help message when no command is provided if (msg.includes("Not enough non-option arguments")) { yarg.showHelp(); - // eslint-disable-next-line no-console + // biome-ignore lint/suspicious/noConsoleLog: This code will run only in browser so console will be available. console.log("\n"); } } @@ -22,7 +22,6 @@ void prover const errorMessage = err !== undefined ? (err instanceof YargsError ? err.message : err.stack) : msg || "Unknown error"; - // eslint-disable-next-line no-console console.error(` ✖ ${errorMessage}\n`); process.exit(1); }) diff --git a/packages/prover/src/interfaces.ts b/packages/prover/src/interfaces.ts index 36222c1476d3..9a67b47ea3b3 100644 --- a/packages/prover/src/interfaces.ts +++ b/packages/prover/src/interfaces.ts @@ -27,7 +27,7 @@ export type ELRequestHandler = ( payload: JsonRpcRequestOrBatch ) => Promise | undefined>; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export type ELRequestHandlerAny = ELRequestHandler; /** diff --git a/packages/prover/src/proof_provider/payload_store.ts b/packages/prover/src/proof_provider/payload_store.ts index 343ec63719f9..c891cb994da1 100644 --- a/packages/prover/src/proof_provider/payload_store.ts +++ b/packages/prover/src/proof_provider/payload_store.ts @@ -39,7 +39,7 @@ export class PayloadStore { const maxBlockNumberForFinalized = this.finalizedRoots.max; if (maxBlockNumberForFinalized === undefined) { - return; + return undefined; } const finalizedMaxRoot = this.finalizedRoots.get(maxBlockNumberForFinalized); diff --git a/packages/prover/src/types.ts b/packages/prover/src/types.ts index 781ba1f7b207..d404cd96d4d8 100644 --- a/packages/prover/src/types.ts +++ b/packages/prover/src/types.ts @@ -45,17 +45,17 @@ export interface JsonRpcResponseWithErrorPayload { } // Make the very flexible el response type to match different libraries easily -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export type JsonRpcResponse = | JsonRpcResponseWithResultPayload | JsonRpcResponseWithErrorPayload; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export type JsonRpcBatchResponse = JsonRpcResponse[]; // Response can be a single response or an array of responses in case of batch request // Make the very flexible el response type to match different libraries easily -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export type JsonRpcResponseOrBatch = JsonRpcResponse | JsonRpcBatchResponse; export type HexString = string; @@ -145,7 +145,6 @@ export interface ELAccessListResponse { export type ELStorageProof = Pick; -/* eslint-disable @typescript-eslint/naming-convention */ export type ELApi = { eth_getBalance: (address: string, block?: number | string) => string; eth_createAccessList: (transaction: ELTransaction, block?: ELBlockNumberOrTag) => ELAccessListResponse; diff --git a/packages/prover/src/utils/conversion.ts b/packages/prover/src/utils/conversion.ts index c809de5c4555..77c3141f828a 100644 --- a/packages/prover/src/utils/conversion.ts +++ b/packages/prover/src/utils/conversion.ts @@ -58,7 +58,6 @@ export function headerDataFromELBlock(blockInfo: ELBlock): HeaderData { }; } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function txDataFromELBlock(txInfo: ELTransaction) { return { ...txInfo, diff --git a/packages/prover/src/utils/evm.ts b/packages/prover/src/utils/evm.ts index 19f43d9584c8..40950647b8f3 100644 --- a/packages/prover/src/utils/evm.ts +++ b/packages/prover/src/utils/evm.ts @@ -20,7 +20,7 @@ export async function createVM({proofProvider}: {proofProvider: ProofProvider}): const blockchain = await Blockchain.create({common}); // Connect blockchain object with existing proof provider for block history - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: (blockchain as any).getBlock = async (blockId: number) => { const payload = await proofProvider.getExecutionPayload(blockId); return { diff --git a/packages/prover/src/utils/file.ts b/packages/prover/src/utils/file.ts index d236d2d5dc95..acc62033010f 100644 --- a/packages/prover/src/utils/file.ts +++ b/packages/prover/src/utils/file.ts @@ -15,7 +15,6 @@ const yamlSchema = FAILSAFE_SCHEMA.extend({ new Type("tag:yaml.org,2002:str", { kind: "scalar", construct: function construct(data) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return data !== null ? data : ""; }, }), diff --git a/packages/prover/src/utils/gitData/gitDataPath.ts b/packages/prover/src/utils/gitData/gitDataPath.ts index e243ca433f2d..1ad3104aafc6 100644 --- a/packages/prover/src/utils/gitData/gitDataPath.ts +++ b/packages/prover/src/utils/gitData/gitDataPath.ts @@ -4,7 +4,6 @@ import {fileURLToPath} from "node:url"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Persist git data and distribute through NPM so CLI consumers can know exactly diff --git a/packages/prover/src/utils/gitData/index.ts b/packages/prover/src/utils/gitData/index.ts index 96c5e4bbaef2..0720d39d9e30 100644 --- a/packages/prover/src/utils/gitData/index.ts +++ b/packages/prover/src/utils/gitData/index.ts @@ -11,7 +11,7 @@ export function readAndGetGitData(): GitData { let persistedGitData: Partial; try { persistedGitData = readGitDataFile(); - } catch (e) { + } catch (_e) { persistedGitData = {}; } @@ -23,13 +23,13 @@ export function readAndGetGitData(): GitData { branch: currentGitData.branch && currentGitData.branch.length > 0 ? currentGitData.branch - : persistedGitData.branch ?? "", + : (persistedGitData.branch ?? ""), commit: currentGitData.commit && currentGitData.commit.length > 0 ? currentGitData.commit - : persistedGitData.commit ?? "", + : (persistedGitData.commit ?? ""), }; - } catch (e) { + } catch (_e) { return { branch: "", commit: "", @@ -49,7 +49,7 @@ export function getGitData(): GitData { function getBranch(): string { try { return shellSilent("git rev-parse --abbrev-ref HEAD"); - } catch (e) { + } catch (_e) { return ""; } } @@ -58,7 +58,7 @@ function getBranch(): string { function getCommit(): string { try { return shellSilent("git rev-parse --verify HEAD"); - } catch (e) { + } catch (_e) { return ""; } } diff --git a/packages/prover/src/utils/process.ts b/packages/prover/src/utils/process.ts index 26768ffce1fb..75bb79516609 100644 --- a/packages/prover/src/utils/process.ts +++ b/packages/prover/src/utils/process.ts @@ -13,7 +13,7 @@ import {getResponseForRequest, isBatchRequest, isRequest} from "./json_rpc.js"; import {isNullish} from "./validation.js"; import {ELRpcProvider} from "./rpc_provider.js"; -/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any */ +// biome-ignore lint/suspicious/noExplicitAny: export const verifiableMethodHandlers: Record> = { eth_getBalance: eth_getBalance, eth_getTransactionCount: eth_getTransactionCount, @@ -106,7 +106,6 @@ export async function processAndVerifyRequest({ if (responses.length === 1) { return responses[0]; - } else { - return responses; } + return responses; } diff --git a/packages/prover/src/utils/version.ts b/packages/prover/src/utils/version.ts index 4752856e5b1d..624412107603 100644 --- a/packages/prover/src/utils/version.ts +++ b/packages/prover/src/utils/version.ts @@ -6,7 +6,6 @@ import {readAndGetGitData} from "./gitData/index.js"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); type VersionJson = { diff --git a/packages/prover/src/verified_requests/eth_call.ts b/packages/prover/src/verified_requests/eth_call.ts index b28bd222c568..eea7ba146c3f 100644 --- a/packages/prover/src/verified_requests/eth_call.ts +++ b/packages/prover/src/verified_requests/eth_call.ts @@ -8,7 +8,6 @@ import { getVerificationFailedMessage, } from "../utils/json_rpc.js"; -// eslint-disable-next-line @typescript-eslint/naming-convention export const eth_call: ELVerifiedRequestHandler = async ({ rpc, payload, diff --git a/packages/prover/src/verified_requests/eth_estimateGas.ts b/packages/prover/src/verified_requests/eth_estimateGas.ts index fee446aadb5b..4ac4c3ccb6bc 100644 --- a/packages/prover/src/verified_requests/eth_estimateGas.ts +++ b/packages/prover/src/verified_requests/eth_estimateGas.ts @@ -8,7 +8,6 @@ import { getVerificationFailedMessage, } from "../utils/json_rpc.js"; -// eslint-disable-next-line @typescript-eslint/naming-convention export const eth_estimateGas: ELVerifiedRequestHandler< ELApiParams["eth_estimateGas"], ELApiReturn["eth_estimateGas"] diff --git a/packages/prover/src/verified_requests/eth_getBalance.ts b/packages/prover/src/verified_requests/eth_getBalance.ts index b5b7e63dcb64..0c03b23be788 100644 --- a/packages/prover/src/verified_requests/eth_getBalance.ts +++ b/packages/prover/src/verified_requests/eth_getBalance.ts @@ -6,7 +6,6 @@ import { getVerificationFailedMessage, } from "../utils/json_rpc.js"; -// eslint-disable-next-line @typescript-eslint/naming-convention export const eth_getBalance: ELVerifiedRequestHandler<[address: string, block?: number | string], string> = async ({ rpc, payload, diff --git a/packages/prover/src/verified_requests/eth_getBlockByHash.ts b/packages/prover/src/verified_requests/eth_getBlockByHash.ts index cb5fa1711c0f..00a110c01e9a 100644 --- a/packages/prover/src/verified_requests/eth_getBlockByHash.ts +++ b/packages/prover/src/verified_requests/eth_getBlockByHash.ts @@ -7,7 +7,6 @@ import { getVerificationFailedMessage, } from "../utils/json_rpc.js"; -// eslint-disable-next-line @typescript-eslint/naming-convention export const eth_getBlockByHash: ELVerifiedRequestHandler<[block: string, hydrated: boolean], ELBlock> = async ({ rpc, payload, diff --git a/packages/prover/src/verified_requests/eth_getBlockByNumber.ts b/packages/prover/src/verified_requests/eth_getBlockByNumber.ts index a08703881cc0..23e0fa2ca863 100644 --- a/packages/prover/src/verified_requests/eth_getBlockByNumber.ts +++ b/packages/prover/src/verified_requests/eth_getBlockByNumber.ts @@ -7,7 +7,6 @@ import { getVerificationFailedMessage, } from "../utils/json_rpc.js"; -// eslint-disable-next-line @typescript-eslint/naming-convention export const eth_getBlockByNumber: ELVerifiedRequestHandler< [block: string | number, hydrated: boolean], ELBlock diff --git a/packages/prover/src/verified_requests/eth_getCode.ts b/packages/prover/src/verified_requests/eth_getCode.ts index 9cb3362c4e50..f94ae8c1c8bd 100644 --- a/packages/prover/src/verified_requests/eth_getCode.ts +++ b/packages/prover/src/verified_requests/eth_getCode.ts @@ -6,7 +6,6 @@ import { getVerificationFailedMessage, } from "../utils/json_rpc.js"; -// eslint-disable-next-line @typescript-eslint/naming-convention export const eth_getCode: ELVerifiedRequestHandler<[address: string, block?: number | string], string> = async ({ rpc, payload, diff --git a/packages/prover/src/verified_requests/eth_getTransactionCount.ts b/packages/prover/src/verified_requests/eth_getTransactionCount.ts index 4cafd9b2b271..aeef67e96e74 100644 --- a/packages/prover/src/verified_requests/eth_getTransactionCount.ts +++ b/packages/prover/src/verified_requests/eth_getTransactionCount.ts @@ -6,7 +6,6 @@ import { getVerificationFailedMessage, } from "../utils/json_rpc.js"; -// eslint-disable-next-line @typescript-eslint/naming-convention export const eth_getTransactionCount: ELVerifiedRequestHandler< [address: string, block?: number | string], string @@ -15,7 +14,6 @@ export const eth_getTransactionCount: ELVerifiedRequestHandler< params: [address, block], } = payload; const result = await verifyAccount({proofProvider, logger, rpc, address, block}); - if (result.valid) { return getResponseForRequest(payload, result.data.nonce); } diff --git a/packages/prover/src/web3_provider_inspector.ts b/packages/prover/src/web3_provider_inspector.ts index c81847b64d61..154a860d0922 100644 --- a/packages/prover/src/web3_provider_inspector.ts +++ b/packages/prover/src/web3_provider_inspector.ts @@ -68,7 +68,7 @@ export class Web3ProviderInspector { return; } - const index = this.providerTypes.findIndex((p) => p.name == indexOrName); + const index = this.providerTypes.findIndex((p) => p.name === indexOrName); if (index < 0) { throw Error(`Provider type '${indexOrName}' is not registered.`); } diff --git a/packages/prover/test/e2e/cli/cmds/start.test.ts b/packages/prover/test/e2e/cli/cmds/start.test.ts index 8ac71e1a1797..75773ac02d36 100644 --- a/packages/prover/test/e2e/cli/cmds/start.test.ts +++ b/packages/prover/test/e2e/cli/cmds/start.test.ts @@ -29,7 +29,7 @@ describe("prover/proxy", () => { const paramsFilePath = path.join("/tmp", "e2e-test-env", "params.json"); const web3: Web3 = new Web3(proxyUrl); - beforeAll(async function () { + beforeAll(async () => { await waitForCapellaFork(); await mkdir(path.dirname(paramsFilePath), {recursive: true}); await writeFile(paramsFilePath, JSON.stringify(chainConfigToJson(config as ChainConfig))); diff --git a/packages/prover/test/e2e/web3_batch_request.test.ts b/packages/prover/test/e2e/web3_batch_request.test.ts index e232208a15b3..6dae04a9c36d 100644 --- a/packages/prover/test/e2e/web3_batch_request.test.ts +++ b/packages/prover/test/e2e/web3_batch_request.test.ts @@ -5,7 +5,7 @@ import {createVerifiedExecutionProvider} from "../../src/web3_provider.js"; import {rpcUrl, beaconUrl, config, waitForCapellaFork, minCapellaTimeMs} from "../utils/e2e_env.js"; import {getVerificationFailedMessage} from "../../src/utils/json_rpc.js"; -describe("web3_batch_requests", function () { +describe("web3_batch_requests", () => { vi.setConfig({hookTimeout: minCapellaTimeMs}); let web3: Web3; diff --git a/packages/prover/test/e2e/web3_provider.test.ts b/packages/prover/test/e2e/web3_provider.test.ts index 3d670d7ed412..edb3bb09e2fb 100644 --- a/packages/prover/test/e2e/web3_provider.test.ts +++ b/packages/prover/test/e2e/web3_provider.test.ts @@ -5,7 +5,7 @@ import {LCTransport} from "../../src/interfaces.js"; import {createVerifiedExecutionProvider} from "../../src/web3_provider.js"; import {waitForCapellaFork, minCapellaTimeMs, rpcUrl, beaconUrl, config} from "../utils/e2e_env.js"; -describe("web3_provider", function () { +describe("web3_provider", () => { vi.setConfig({hookTimeout: minCapellaTimeMs}); beforeAll(async () => { diff --git a/packages/prover/test/fixtures/mainnet/eth_call.json b/packages/prover/test/fixtures/mainnet/eth_call.json index 92e9ee557bc2..a3fe44880626 100644 --- a/packages/prover/test/fixtures/mainnet/eth_call.json +++ b/packages/prover/test/fixtures/mainnet/eth_call.json @@ -5226,4 +5226,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/packages/prover/test/fixtures/mainnet/eth_estimateGas_contract_call.json b/packages/prover/test/fixtures/mainnet/eth_estimateGas_contract_call.json index eaae6bc7190d..a8be70662e2b 100644 --- a/packages/prover/test/fixtures/mainnet/eth_estimateGas_contract_call.json +++ b/packages/prover/test/fixtures/mainnet/eth_estimateGas_contract_call.json @@ -5226,4 +5226,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/packages/prover/test/fixtures/mainnet/eth_estimateGas_simple_transfer.json b/packages/prover/test/fixtures/mainnet/eth_estimateGas_simple_transfer.json index f9b654b1a6fa..606e520a441c 100644 --- a/packages/prover/test/fixtures/mainnet/eth_estimateGas_simple_transfer.json +++ b/packages/prover/test/fixtures/mainnet/eth_estimateGas_simple_transfer.json @@ -5227,4 +5227,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/packages/prover/test/fixtures/sepolia/eth_getBalance_contract.json b/packages/prover/test/fixtures/sepolia/eth_getBalance_contract.json index f6e2a31b2b87..660889c259d7 100644 --- a/packages/prover/test/fixtures/sepolia/eth_getBalance_contract.json +++ b/packages/prover/test/fixtures/sepolia/eth_getBalance_contract.json @@ -2704,4 +2704,4 @@ } } ] -} \ No newline at end of file +} diff --git a/packages/prover/test/fixtures/sepolia/eth_getBalance_eoa.json b/packages/prover/test/fixtures/sepolia/eth_getBalance_eoa.json index 03627b534159..a2489403db5b 100644 --- a/packages/prover/test/fixtures/sepolia/eth_getBalance_eoa.json +++ b/packages/prover/test/fixtures/sepolia/eth_getBalance_eoa.json @@ -2703,4 +2703,4 @@ } } ] -} \ No newline at end of file +} diff --git a/packages/prover/test/fixtures/sepolia/eth_getBlock_with_contractCreation.json b/packages/prover/test/fixtures/sepolia/eth_getBlock_with_contractCreation.json index 6b0471a182cf..c61d0e58e35e 100644 --- a/packages/prover/test/fixtures/sepolia/eth_getBlock_with_contractCreation.json +++ b/packages/prover/test/fixtures/sepolia/eth_getBlock_with_contractCreation.json @@ -5,10 +5,7 @@ "id": 809166, "jsonrpc": "2.0", "method": "eth_getBlockByHash", - "params": [ - "0x3a0225b38d5927a37cc95fd48254e83c4e9b70115918a103d9fd7e36464030d4", - true - ] + "params": ["0x3a0225b38d5927a37cc95fd48254e83c4e9b70115918a103d9fd7e36464030d4", true] }, "response": { "jsonrpc": "2.0", @@ -571,4 +568,4 @@ } }, "dependentRequests": [] -} \ No newline at end of file +} diff --git a/packages/prover/test/fixtures/sepolia/eth_getBlock_with_no_accessList.json b/packages/prover/test/fixtures/sepolia/eth_getBlock_with_no_accessList.json index 59bb888450d1..c6c1f9c8d675 100644 --- a/packages/prover/test/fixtures/sepolia/eth_getBlock_with_no_accessList.json +++ b/packages/prover/test/fixtures/sepolia/eth_getBlock_with_no_accessList.json @@ -5,10 +5,7 @@ "id": 809162, "jsonrpc": "2.0", "method": "eth_getBlockByHash", - "params": [ - "0x75b10426177f0f4bd8683999e2c7c597007c6e7c4551d6336c0f880b12c6f3bf", - true - ] + "params": ["0x75b10426177f0f4bd8683999e2c7c597007c6e7c4551d6336c0f880b12c6f3bf", true] }, "response": { "jsonrpc": "2.0", @@ -2381,4 +2378,4 @@ } }, "dependentRequests": [] -} \ No newline at end of file +} diff --git a/packages/prover/test/fixtures/sepolia/eth_getCode.json b/packages/prover/test/fixtures/sepolia/eth_getCode.json index 1787e8cfd2ce..a15c15698420 100644 --- a/packages/prover/test/fixtures/sepolia/eth_getCode.json +++ b/packages/prover/test/fixtures/sepolia/eth_getCode.json @@ -2704,4 +2704,4 @@ } } ] -} \ No newline at end of file +} diff --git a/packages/prover/test/fixtures/sepolia/eth_getTransactionCount.json b/packages/prover/test/fixtures/sepolia/eth_getTransactionCount.json index ff45a5e8a776..486c44698c16 100644 --- a/packages/prover/test/fixtures/sepolia/eth_getTransactionCount.json +++ b/packages/prover/test/fixtures/sepolia/eth_getTransactionCount.json @@ -2703,4 +2703,4 @@ } } ] -} \ No newline at end of file +} diff --git a/packages/prover/test/mocks/request_handler.ts b/packages/prover/test/mocks/request_handler.ts index bbe4f24057de..edde1e59a3fe 100644 --- a/packages/prover/test/mocks/request_handler.ts +++ b/packages/prover/test/mocks/request_handler.ts @@ -116,7 +116,6 @@ export function generateReqHandlerOptionsMock( getExecutionPayload: vi.fn().mockResolvedValue(executionPayload), config: { ...config, - // eslint-disable-next-line @typescript-eslint/naming-convention PRESET_BASE: data.network as unknown as PresetName, }, network: data.network, diff --git a/packages/prover/test/tsconfig.json b/packages/prover/test/tsconfig.json index 7e6bad81b22f..f4241fc1fbcd 100644 --- a/packages/prover/test/tsconfig.json +++ b/packages/prover/test/tsconfig.json @@ -3,4 +3,4 @@ "compilerOptions": { "noEmit": false } -} \ No newline at end of file +} 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 b482bb579e77..6bc1e4265205 100644 --- a/packages/prover/test/unit/proof_provider/payload_store.test.ts +++ b/packages/prover/test/unit/proof_provider/payload_store.test.ts @@ -53,7 +53,7 @@ const buildBlockResponse = ({ return apiResponse; }; -describe("proof_provider/payload_store", function () { +describe("proof_provider/payload_store", () => { let api: ApiClient & {beacon: MockedObject}; let logger: Logger; let store: PayloadStore; diff --git a/packages/prover/test/unit/verified_requests/eth_getBlockByHash.test.ts b/packages/prover/test/unit/verified_requests/eth_getBlockByHash.test.ts index fca484657c01..3175bdd6d60c 100644 --- a/packages/prover/test/unit/verified_requests/eth_getBlockByHash.test.ts +++ b/packages/prover/test/unit/verified_requests/eth_getBlockByHash.test.ts @@ -3,8 +3,12 @@ import {createForkConfig} from "@lodestar/config"; import {NetworkName, networksChainConfig} from "@lodestar/config/networks"; import {VERIFICATION_FAILED_RESPONSE_CODE} from "../../../src/constants.js"; import {eth_getBlockByHash} from "../../../src/verified_requests/eth_getBlockByHash.js"; -import ethGetBlockWithContractCreation from "../../fixtures/sepolia/eth_getBlock_with_contractCreation.json" assert {type: "json"}; -import ethGetBlockWithNoAccessList from "../../fixtures/sepolia/eth_getBlock_with_no_accessList.json" assert {type: "json"}; +import ethGetBlockWithContractCreation from "../../fixtures/sepolia/eth_getBlock_with_contractCreation.json" assert { + type: "json", +}; +import ethGetBlockWithNoAccessList from "../../fixtures/sepolia/eth_getBlock_with_no_accessList.json" assert { + type: "json", +}; import {TestFixture, cloneTestFixture, generateReqHandlerOptionsMock} from "../../mocks/request_handler.js"; import {ELBlock} from "../../../src/types.js"; import {getVerificationFailedMessage} from "../../../src/utils/json_rpc.js"; diff --git a/packages/prover/test/unit/verified_requests/eth_getBlockByNumber.test.ts b/packages/prover/test/unit/verified_requests/eth_getBlockByNumber.test.ts index 81f644a46024..cc1389128ec0 100644 --- a/packages/prover/test/unit/verified_requests/eth_getBlockByNumber.test.ts +++ b/packages/prover/test/unit/verified_requests/eth_getBlockByNumber.test.ts @@ -4,8 +4,12 @@ import {NetworkName, networksChainConfig} from "@lodestar/config/networks"; import {VERIFICATION_FAILED_RESPONSE_CODE} from "../../../src/constants.js"; import {ELBlock} from "../../../src/types.js"; import {eth_getBlockByNumber} from "../../../src/verified_requests/eth_getBlockByNumber.js"; -import ethGetBlockWithContractCreation from "../../fixtures/sepolia/eth_getBlock_with_contractCreation.json" assert {type: "json"}; -import ethGetBlockWithNoAccessList from "../../fixtures/sepolia/eth_getBlock_with_no_accessList.json" assert {type: "json"}; +import ethGetBlockWithContractCreation from "../../fixtures/sepolia/eth_getBlock_with_contractCreation.json" assert { + type: "json", +}; +import ethGetBlockWithNoAccessList from "../../fixtures/sepolia/eth_getBlock_with_no_accessList.json" assert { + type: "json", +}; import {TestFixture, cloneTestFixture, generateReqHandlerOptionsMock} from "../../mocks/request_handler.js"; import {getVerificationFailedMessage} from "../../../src/utils/json_rpc.js"; @@ -88,7 +92,6 @@ describe("verified_requests / eth_getBlockByNumber", () => { // Temper the execution payload const testCase = cloneTestFixture(t, { beacon: { - // eslint-disable-next-line @typescript-eslint/naming-convention executionPayload: {parent_hash: "0xbdbd90ab601a073c3d128111eafb12fa7ece4af239abdc8be60184a08c6d7ef4"}, }, }); diff --git a/packages/prover/test/utils/e2e_env.ts b/packages/prover/test/utils/e2e_env.ts index b63d276daa5f..17af3739569a 100644 --- a/packages/prover/test/utils/e2e_env.ts +++ b/packages/prover/test/utils/e2e_env.ts @@ -1,6 +1,5 @@ import {waitForEndpoint} from "@lodestar/test-utils"; -/* eslint-disable @typescript-eslint/naming-convention */ export const rpcUrl = "http://0.0.0.0:8001"; export const beaconUrl = "http://0.0.0.0:5001"; export const proxyPort = 8888; diff --git a/packages/reqresp/package.json b/packages/reqresp/package.json index 44dcb8048f99..981016a363df 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.22.0", + "version": "1.23.0", "type": "module", "exports": { ".": { @@ -45,8 +45,8 @@ "build:release": "yarn clean && yarn run build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit", "test:unit": "vitest --run --dir test/unit/", "check-readme": "typescript-docs-verifier" @@ -54,9 +54,9 @@ "dependencies": { "@chainsafe/fast-crc32c": "^4.1.1", "@libp2p/interface": "^1.3.0", - "@lodestar/config": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/utils": "^1.22.0", + "@lodestar/config": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/utils": "^1.23.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.22.0", - "@lodestar/types": "^1.22.0", + "@lodestar/logger": "^1.23.0", + "@lodestar/types": "^1.23.0", "libp2p": "1.4.3" }, "peerDependencies": { diff --git a/packages/reqresp/src/encoders/responseDecode.ts b/packages/reqresp/src/encoders/responseDecode.ts index 93ebff674ec6..0dde5bcdc95e 100644 --- a/packages/reqresp/src/encoders/responseDecode.ts +++ b/packages/reqresp/src/encoders/responseDecode.ts @@ -110,7 +110,7 @@ export async function readResultHeader(bufferedSource: BufferedSource): Promise< */ export async function readErrorMessage(bufferedSource: BufferedSource): Promise { // Read at least 256 or wait for the stream to end - let length; + let length: number | undefined; for await (const buffer of bufferedSource) { // Wait for next chunk with bytes or for the stream to end // Note: The entire is expected to be in the same chunk @@ -121,13 +121,13 @@ export async function readErrorMessage(bufferedSource: BufferedSource): Promise< length = buffer.length; } + // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute const bytes = bufferedSource["buffer"].slice(0, length); try { return decodeErrorMessage(bytes); - } catch { + } catch (_e) { // Error message is optional and may not be included in the response stream - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call return Buffer.prototype.toString.call(bytes, "hex"); } } diff --git a/packages/reqresp/src/encodingStrategies/sszSnappy/decode.ts b/packages/reqresp/src/encodingStrategies/sszSnappy/decode.ts index 1bf9fc815e28..9ebe52876cfe 100644 --- a/packages/reqresp/src/encodingStrategies/sszSnappy/decode.ts +++ b/packages/reqresp/src/encodingStrategies/sszSnappy/decode.ts @@ -35,7 +35,7 @@ export async function readSszSnappyHeader(bufferedSource: BufferedSource, type: let sszDataLength: number; try { sszDataLength = varintDecode(buffer.subarray()); - } catch (e) { + } catch (_e) { throw new SszSnappyError({code: SszSnappyErrorCode.INVALID_VARINT_BYTES_COUNT, bytes: Infinity}); } diff --git a/packages/reqresp/src/encodingStrategies/sszSnappy/errors.ts b/packages/reqresp/src/encodingStrategies/sszSnappy/errors.ts index 12113ceb42fb..f9d6c4a8e9b5 100644 --- a/packages/reqresp/src/encodingStrategies/sszSnappy/errors.ts +++ b/packages/reqresp/src/encodingStrategies/sszSnappy/errors.ts @@ -28,8 +28,4 @@ type SszSnappyErrorType = | {code: SszSnappyErrorCode.TOO_MANY_BYTES; sszDataLength: number} | {code: SszSnappyErrorCode.SOURCE_ABORTED}; -export class SszSnappyError extends LodestarError { - constructor(type: SszSnappyErrorType) { - super(type); - } -} +export class SszSnappyError extends LodestarError {} diff --git a/packages/reqresp/src/encodingStrategies/sszSnappy/snappyFrames/uncompress.ts b/packages/reqresp/src/encodingStrategies/sszSnappy/snappyFrames/uncompress.ts index e97260989082..8530bc7aeb88 100644 --- a/packages/reqresp/src/encodingStrategies/sszSnappy/snappyFrames/uncompress.ts +++ b/packages/reqresp/src/encodingStrategies/sszSnappy/snappyFrames/uncompress.ts @@ -35,7 +35,6 @@ export class SnappyFramesUncompress { } if (type === ChunkType.IDENTIFIER) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call if (!Buffer.prototype.equals.call(data, IDENTIFIER)) { throw "malformed input: bad identifier"; } @@ -52,9 +51,8 @@ export class SnappyFramesUncompress { } if (result.length === 0) { return null; - } else { - return result; } + return result; } reset(): void { diff --git a/packages/reqresp/src/metrics.ts b/packages/reqresp/src/metrics.ts index 4af18a782322..faf4b7cf65ae 100644 --- a/packages/reqresp/src/metrics.ts +++ b/packages/reqresp/src/metrics.ts @@ -5,7 +5,6 @@ export type Metrics = ReturnType; /** * A collection of metrics used throughout the Gossipsub behaviour. */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function getMetrics(register: MetricsRegister) { // Using function style instead of class to prevent having to re-declare all MetricsPrometheus types. diff --git a/packages/reqresp/src/rate_limiter/ReqRespRateLimiter.ts b/packages/reqresp/src/rate_limiter/ReqRespRateLimiter.ts index 66dfbd9f1cc7..7bb87cbd1fb2 100644 --- a/packages/reqresp/src/rate_limiter/ReqRespRateLimiter.ts +++ b/packages/reqresp/src/rate_limiter/ReqRespRateLimiter.ts @@ -68,9 +68,9 @@ export class ReqRespRateLimiter { if ((byPeer && !byPeer.allows(peerIdStr, requestCount)) || (total && !total.allows(null, requestCount))) { this.opts?.onRateLimit?.(peerId, protocolID); return false; - } else { - return true; } + + return true; } prune(peerId: PeerId): void { diff --git a/packages/reqresp/src/request/index.ts b/packages/reqresp/src/request/index.ts index 4920a32eb221..9a374db3b8be 100644 --- a/packages/reqresp/src/request/index.ts +++ b/packages/reqresp/src/request/index.ts @@ -98,7 +98,6 @@ export async function* sendRequest( async (timeoutAndParentSignal) => { const protocolIds = Array.from(protocolsMap.keys()); const conn = await libp2p.dialProtocol(peerId, protocolIds, {signal: timeoutAndParentSignal}); - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!conn) throw Error("dialProtocol timeout"); return conn; }, @@ -107,9 +106,8 @@ export async function* sendRequest( ).catch((e: Error) => { if (e instanceof TimeoutError) { throw new RequestError({code: RequestErrorCode.DIAL_TIMEOUT}); - } else { - throw new RequestError({code: RequestErrorCode.DIAL_ERROR, error: e}); } + throw new RequestError({code: RequestErrorCode.DIAL_ERROR, error: e}); }); // TODO: Does the TTFB timer start on opening stream or after receiving request @@ -134,9 +132,8 @@ export async function* sendRequest( if (e instanceof TimeoutError) { throw new RequestError({code: RequestErrorCode.REQUEST_TIMEOUT}); - } else { - throw new RequestError({code: RequestErrorCode.REQUEST_ERROR, error: e as Error}); } + throw new RequestError({code: RequestErrorCode.REQUEST_ERROR, error: e as Error}); } ); @@ -210,8 +207,7 @@ export async function* sendRequest( if (e instanceof ResponseError) { throw new RequestError(responseStatusErrorToRequestError(e)); - } else { - throw e; } + throw e; } } diff --git a/packages/reqresp/src/response/index.ts b/packages/reqresp/src/response/index.ts index 27758caa3f24..d9f0e1b1806a 100644 --- a/packages/reqresp/src/response/index.ts +++ b/packages/reqresp/src/response/index.ts @@ -76,10 +76,9 @@ export async function handleRequest({ signal ).catch((e: unknown) => { if (e instanceof TimeoutError) { - throw e; // Let outter catch {} re-type the error as SERVER_ERROR - } else { - throw new ResponseError(RespStatus.INVALID_REQUEST, (e as Error).message); + throw e; // Let outter catch (_e) {} re-type the error as SERVER_ERROR } + throw new ResponseError(RespStatus.INVALID_REQUEST, (e as Error).message); }); logger.debug("Req received", logCtx); @@ -134,8 +133,7 @@ export async function handleRequest({ if (responseError !== null) { logger.verbose("Resp error", logCtx, responseError); throw responseError; - } else { - // NOTE: Only log once per request to verbose, intermediate steps to debug - logger.verbose("Resp done", logCtx); } + // NOTE: Only log once per request to verbose, intermediate steps to debug + logger.verbose("Resp done", logCtx); } diff --git a/packages/reqresp/src/utils/abortableSource.ts b/packages/reqresp/src/utils/abortableSource.ts index 17ea836bcac3..127339bbd597 100644 --- a/packages/reqresp/src/utils/abortableSource.ts +++ b/packages/reqresp/src/utils/abortableSource.ts @@ -53,9 +53,9 @@ export function abortableSource( if (result.done) { return; - } else { - yield result.value; } + + yield result.value; } } catch (err) { // End the iterator if it is a generator diff --git a/packages/reqresp/src/utils/bufferedSource.ts b/packages/reqresp/src/utils/bufferedSource.ts index ac31a62ba29c..e5daca1d2f67 100644 --- a/packages/reqresp/src/utils/bufferedSource.ts +++ b/packages/reqresp/src/utils/bufferedSource.ts @@ -18,7 +18,6 @@ export class BufferedSource { } [Symbol.asyncIterator](): AsyncIterator { - // eslint-disable-next-line @typescript-eslint/no-this-alias const that = this; let firstNext = true; @@ -32,16 +31,15 @@ export class BufferedSource { return {done: false, value: that.buffer}; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const {done, value: chunk} = await that.source.next(); if (done === true) { that.isDone = true; return {done: true, value: undefined}; - } else { - // Concat new chunk and return a reference to its BufferList instance - that.buffer.append(chunk); - return {done: false, value: that.buffer}; } + + // Concat new chunk and return a reference to its BufferList instance + that.buffer.append(chunk); + return {done: false, value: that.buffer}; }, }; } diff --git a/packages/reqresp/test/fixtures/encodingStrategies.ts b/packages/reqresp/test/fixtures/encodingStrategies.ts index c7705bb9493b..7bf183d8714c 100644 --- a/packages/reqresp/test/fixtures/encodingStrategies.ts +++ b/packages/reqresp/test/fixtures/encodingStrategies.ts @@ -15,7 +15,6 @@ import { // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); type SszSnappyTestBlockData = { diff --git a/packages/reqresp/test/fixtures/messages.ts b/packages/reqresp/test/fixtures/messages.ts index 5558e7c3e13a..a243a05b221b 100644 --- a/packages/reqresp/test/fixtures/messages.ts +++ b/packages/reqresp/test/fixtures/messages.ts @@ -14,7 +14,6 @@ type MessageFixture = { }; const phase0Metadata = ssz.phase0.Metadata.fromJson({ - // eslint-disable-next-line @typescript-eslint/naming-convention seq_number: "9", attnets: "0x0000000000000000", }); @@ -37,7 +36,6 @@ export const sszSnappyPhase0Metadata: MessageFixture = { }; const altairMetadata = ssz.altair.Metadata.fromJson({ - // eslint-disable-next-line @typescript-eslint/naming-convention seq_number: "8", attnets: "0x0000000000000000", syncnets: "0x00", @@ -178,7 +176,6 @@ if (slotBlockAltair - slotBlockPhase0 < SLOTS_PER_EPOCH) { throw Error("phase0 block slot must be an epoch apart from altair block slot"); } const ALTAIR_FORK_EPOCH = Math.floor(slotBlockAltair / SLOTS_PER_EPOCH); -// eslint-disable-next-line @typescript-eslint/naming-convention export const beaconConfig = createBeaconConfig({...chainConfig, ALTAIR_FORK_EPOCH}, ZERO_HASH); export const getEmptyHandler = () => async function* emptyHandler(): AsyncGenerator {}; diff --git a/packages/reqresp/test/fixtures/protocols.ts b/packages/reqresp/test/fixtures/protocols.ts index f20d891781ef..19e8936fc376 100644 --- a/packages/reqresp/test/fixtures/protocols.ts +++ b/packages/reqresp/test/fixtures/protocols.ts @@ -5,7 +5,6 @@ import {ContextBytesType, DialOnlyProtocol, Encoding, ProtocolHandler, Protocol} import {getEmptyHandler} from "./messages.js"; import {beaconConfig} from "./messages.js"; -// eslint-disable-next-line @typescript-eslint/naming-convention const NumToStrReq = new ContainerType( { value: new UintNumberType(4), @@ -15,7 +14,6 @@ const NumToStrReq = new ContainerType( export type NumToStrReqType = ValueOf; -// eslint-disable-next-line @typescript-eslint/naming-convention const NumToStrResp = new ContainerType( { value: new ListBasicType(new UintNumberType(1), 4), diff --git a/packages/reqresp/test/unit/encodingStrategies/sszSnappy/snappyFrames/uncompress.test.ts b/packages/reqresp/test/unit/encodingStrategies/sszSnappy/snappyFrames/uncompress.test.ts index b47621082a65..dc01a1952142 100644 --- a/packages/reqresp/test/unit/encodingStrategies/sszSnappy/snappyFrames/uncompress.test.ts +++ b/packages/reqresp/test/unit/encodingStrategies/sszSnappy/snappyFrames/uncompress.test.ts @@ -4,7 +4,7 @@ import {pipe} from "it-pipe"; import {SnappyFramesUncompress} from "../../../../../src/encodingStrategies/sszSnappy/snappyFrames/uncompress.js"; import {encodeSnappy} from "../../../../../src/encodingStrategies/sszSnappy/snappyFrames/compress.js"; -describe("encodingStrategies / sszSnappy / snappy frames / uncompress", function () { +describe("encodingStrategies / sszSnappy / snappy frames / uncompress", () => { it("should work with short input", () => new Promise((done) => { const testData = "Small test data"; @@ -12,7 +12,7 @@ describe("encodingStrategies / sszSnappy / snappy frames / uncompress", function const decompress = new SnappyFramesUncompress(); - void pipe(compressIterable, async function (source) { + void pipe(compressIterable, async (source) => { for await (const data of source) { const result = decompress.uncompress(new Uint8ArrayList(data)); if (result) { @@ -30,7 +30,7 @@ describe("encodingStrategies / sszSnappy / snappy frames / uncompress", function let result = Buffer.alloc(0); const decompress = new SnappyFramesUncompress(); - void pipe(compressIterable, async function (source) { + void pipe(compressIterable, async (source) => { for await (const data of source) { // testData will come compressed as two or more chunks result = Buffer.concat([ @@ -45,13 +45,13 @@ describe("encodingStrategies / sszSnappy / snappy frames / uncompress", function }); })); - it("should detect malformed input", function () { + it("should detect malformed input", () => { const decompress = new SnappyFramesUncompress(); expect(() => decompress.uncompress(new Uint8ArrayList(Buffer.alloc(32, 5)))).toThrow(); }); - it("should return null if not enough data", function () { + it("should return null if not enough data", () => { const decompress = new SnappyFramesUncompress(); expect(decompress.uncompress(new Uint8ArrayList(Buffer.alloc(3, 1)))).toBe(null); diff --git a/packages/reqresp/test/utils/errors.ts b/packages/reqresp/test/utils/errors.ts index 16c098f2f57c..e4697a6d97d5 100644 --- a/packages/reqresp/test/utils/errors.ts +++ b/packages/reqresp/test/utils/errors.ts @@ -53,9 +53,10 @@ export function expectLodestarError(err1: LodestarErro export function getErrorMetadata(err: LodestarError | Error | unknown): unknown { if (err instanceof LodestarError) { return mapValues(err.getMetadata(), (value) => getErrorMetadata(value as any)); - } else if (err instanceof Error) { + } + + if (err instanceof Error) { return err.message; - } else { - return err; } + return err; } diff --git a/packages/spec-test-util/package.json b/packages/spec-test-util/package.json index b4aabf0140e8..6f58e55d2201 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.22.0", + "version": "1.23.0", "description": "Spec test suite generator from yaml test files", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -44,8 +44,8 @@ "build:watch": "yarn run build --watch", "check-build": "node -e \"(async function() { await import('./lib/downloadTests.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit && yarn test:e2e", "test:unit": "vitest --run --passWithNoTests --dir test/unit/", "test:e2e": "vitest --run --config vitest.e2e.config.ts --dir test/e2e/", @@ -62,7 +62,7 @@ "blockchain" ], "dependencies": { - "@lodestar/utils": "^1.22.0", + "@lodestar/utils": "^1.23.0", "axios": "^1.3.4", "rimraf": "^4.4.1", "snappyjs": "^0.7.0", diff --git a/packages/spec-test-util/src/downloadTests.ts b/packages/spec-test-util/src/downloadTests.ts index e13478934927..bca6a8ac52c9 100644 --- a/packages/spec-test-util/src/downloadTests.ts +++ b/packages/spec-test-util/src/downloadTests.ts @@ -51,9 +51,8 @@ export async function downloadGenericSpecTests( if (existingVersion === specVersion) { return log(`version ${specVersion} already downloaded`); - } else { - log(`Downloading new version ${specVersion}`); } + log(`Downloading new version ${specVersion}`); if (fs.existsSync(outputDir)) { log(`Cleaning existing version ${existingVersion} at ${outputDir}`); diff --git a/packages/spec-test-util/src/single.ts b/packages/spec-test-util/src/single.ts index 47bf8d0a0742..888c87e3ffa8 100644 --- a/packages/spec-test-util/src/single.ts +++ b/packages/spec-test-util/src/single.ts @@ -68,6 +68,7 @@ export interface SpecTestOptions { * Optionally pass function to transform loaded values * (values from input files) */ + inputProcessing?: {[K: string]: (value: any) => any}; shouldError?: (testCase: TestCase) => boolean; @@ -86,7 +87,7 @@ const defaultOptions: SpecTestOptions = { getExpected: (testCase) => testCase, shouldError: () => false, shouldSkip: () => false, - expectFunc: (testCase, expected, actual) => expect(actual).to.be.deep.equal(expected), + expectFunc: (_testCase, expected, actual) => expect(actual).to.be.deep.equal(expected), timeout: 10 * 60 * 1000, }; @@ -101,7 +102,7 @@ export function describeDirectorySpecTest throw new Error(`${testCaseDirectoryPath} is not directory`); } - describe(name, function () { + describe(name, () => { if (options.timeout !== undefined) { vi.setConfig({testTimeout: options.timeout ?? 10 * 60 * 1000}); } @@ -114,7 +115,7 @@ export function describeDirectorySpecTest // Use full path here, not just `testSubDirname` to allow usage of `vitest -t` const testName = `${name}/${testSubDirname}`; - it(testName, async function (context) { + it(testName, async (context) => { // some tests require to load meta.yaml first in order to know respective ssz types. const metaFilePath = path.join(testSubDirPath, "meta.yaml"); const meta: TestCase["meta"] = fs.existsSync(metaFilePath) @@ -123,7 +124,7 @@ export function describeDirectorySpecTest let testCase = loadInputFiles(testSubDirPath, options, meta); if (options.mapToTestCase) testCase = options.mapToTestCase(testCase); - if (options.shouldSkip && options.shouldSkip(testCase, testName, 0)) { + if (options.shouldSkip?.(testCase, testName, 0)) { context.skip(); return; } @@ -131,7 +132,7 @@ export function describeDirectorySpecTest if (options.shouldError?.(testCase)) { try { await testFunction(testCase, name); - } catch (e) { + } catch (_e) { return; } } else { @@ -156,7 +157,8 @@ function loadInputFiles( meta?: TestCase["meta"] ): TestCase { const testCase: any = {}; - fs.readdirSync(directory) + const files = fs + .readdirSync(directory) .map((name) => path.join(directory, name)) .filter((file) => { if (isDirectory(file)) { @@ -169,33 +171,37 @@ function loadInputFiles( options.inputTypes[name] = inputType; const extension = inputType.type as string; return file.endsWith(extension); - }) - .forEach((file) => { - const inputName = path.basename(file).replace(".ssz_snappy", "").replace(".ssz", "").replace(".yaml", ""); - const inputType = getInputType(file); - testCase[inputName] = deserializeInputFile(file, inputName, inputType, options, meta); - switch (inputType) { - case InputType.SSZ: - testCase[`${inputName}_raw`] = fs.readFileSync(file); - break; - case InputType.SSZ_SNAPPY: - testCase[`${inputName}_raw`] = uncompress(fs.readFileSync(file)); - break; - } - if (!options.inputProcessing) throw Error("inputProcessing is not defined"); - if (options.inputProcessing[inputName] !== undefined) { - testCase[inputName] = options.inputProcessing[inputName](testCase[inputName]); - } }); + for (const file of files) { + const inputName = path.basename(file).replace(".ssz_snappy", "").replace(".ssz", "").replace(".yaml", ""); + const inputType = getInputType(file); + testCase[inputName] = deserializeInputFile(file, inputName, inputType, options, meta); + switch (inputType) { + case InputType.SSZ: + testCase[`${inputName}_raw`] = fs.readFileSync(file); + break; + case InputType.SSZ_SNAPPY: + testCase[`${inputName}_raw`] = uncompress(fs.readFileSync(file)); + break; + } + if (!options.inputProcessing) throw Error("inputProcessing is not defined"); + if (options.inputProcessing[inputName] !== undefined) { + testCase[inputName] = options.inputProcessing[inputName](testCase[inputName]); + } + } return testCase as TestCase; } function getInputType(filename: string): InputType { if (filename.endsWith(InputType.YAML)) { return InputType.YAML; - } else if (filename.endsWith(InputType.SSZ_SNAPPY)) { + } + + if (filename.endsWith(InputType.SSZ_SNAPPY)) { return InputType.SSZ_SNAPPY; - } else if (filename.endsWith(InputType.SSZ)) { + } + + if (filename.endsWith(InputType.SSZ)) { return InputType.SSZ; } throw new Error(`Could not get InputType from ${filename}`); @@ -210,7 +216,9 @@ function deserializeInputFile( ): any { if (inputType === InputType.YAML) { return loadYaml(fs.readFileSync(file, "utf8")); - } else if (inputType === InputType.SSZ || inputType === InputType.SSZ_SNAPPY) { + } + + if (inputType === InputType.SSZ || inputType === InputType.SSZ_SNAPPY) { const sszTypes = options.getSszTypes ? options.getSszTypes(meta) : options.sszTypes; if (!sszTypes) throw Error("sszTypes is not defined"); let data = fs.readFileSync(file); @@ -238,9 +246,8 @@ function deserializeInputFile( throw Error("BeaconState type has no deserializeToViewDU method"); } return sszType.deserializeToViewDU(data); - } else { - return sszType.deserialize(data); } + return sszType.deserialize(data); } } diff --git a/packages/spec-test-util/test/e2e/single/index.test.ts b/packages/spec-test-util/test/e2e/single/index.test.ts index 849b0cead30d..2dbefbb9cd22 100644 --- a/packages/spec-test-util/test/e2e/single/index.test.ts +++ b/packages/spec-test-util/test/e2e/single/index.test.ts @@ -8,11 +8,8 @@ import {describeDirectorySpecTest, InputType, loadYamlFile} from "../../../src/s // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); -/* eslint-disable @typescript-eslint/naming-convention */ - export type SimpleStruct = { test: boolean; number: number; diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index 2df87a522a63..1c1d0f0c7754 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.22.0", + "version": "1.23.0", "type": "module", "exports": { ".": { @@ -50,8 +50,8 @@ "build:release": "yarn clean && yarn build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit", "test:unit": "vitest --run --dir test/unit/", "check-readme": "typescript-docs-verifier" @@ -59,14 +59,16 @@ "types": "lib/index.d.ts", "dependencies": { "@chainsafe/as-sha256": "^0.5.0", - "@chainsafe/blst": "^2.0.3", + "@chainsafe/blst": "^2.2.0", "@chainsafe/persistent-merkle-tree": "^0.8.0", "@chainsafe/persistent-ts": "^0.19.1", - "@chainsafe/ssz": "^0.17.1", - "@lodestar/config": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/types": "^1.22.0", - "@lodestar/utils": "^1.22.0", + "@chainsafe/pubkey-index-map": "2.0.0", + "@chainsafe/ssz": "^0.18.0", + "@chainsafe/swap-or-not-shuffle": "^0.0.2", + "@lodestar/config": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0", "bigint-buffer": "^1.1.5", "immutable": "^4.3.2" }, diff --git a/packages/state-transition/src/block/isValidIndexedAttestation.ts b/packages/state-transition/src/block/isValidIndexedAttestation.ts index 33d92a208260..8c7502ceedb5 100644 --- a/packages/state-transition/src/block/isValidIndexedAttestation.ts +++ b/packages/state-transition/src/block/isValidIndexedAttestation.ts @@ -18,9 +18,8 @@ export function isValidIndexedAttestation( if (verifySignature) { return verifySignatureSet(getIndexedAttestationSignatureSet(state, indexedAttestation)); - } else { - return true; } + return true; } export function isValidIndexedAttestationBigint( @@ -34,9 +33,8 @@ export function isValidIndexedAttestationBigint( if (verifySignature) { return verifySignatureSet(getIndexedAttestationBigintSignatureSet(state, indexedAttestation)); - } else { - return true; } + return true; } /** diff --git a/packages/state-transition/src/block/processAttestationPhase0.ts b/packages/state-transition/src/block/processAttestationPhase0.ts index ba6bc9089693..e2b32bbbbee8 100644 --- a/packages/state-transition/src/block/processAttestationPhase0.ts +++ b/packages/state-transition/src/block/processAttestationPhase0.ts @@ -91,13 +91,13 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo if (committeeIndices.length === 0) { throw Error("Attestation should have at least one committee bit set"); - } else { - const lastCommitteeIndex = committeeIndices[committeeIndices.length - 1]; - if (lastCommitteeIndex >= committeeCount) { - throw new Error( - `Attestation committee index exceeds committee count: lastCommitteeIndex=${lastCommitteeIndex} numCommittees=${committeeCount}` - ); - } + } + + const lastCommitteeIndex = committeeIndices[committeeIndices.length - 1]; + if (lastCommitteeIndex >= committeeCount) { + throw new Error( + `Attestation committee index exceeds committee count: lastCommitteeIndex=${lastCommitteeIndex} numCommittees=${committeeCount}` + ); } // Get total number of attestation participant of every committee specified @@ -133,9 +133,8 @@ export function isTimelyTarget(fork: ForkSeq, inclusionDistance: Slot): boolean // post deneb attestation is valid till end of next epoch for target if (fork >= ForkSeq.deneb) { return true; - } else { - return inclusionDistance <= SLOTS_PER_EPOCH; } + return inclusionDistance <= SLOTS_PER_EPOCH; } export function checkpointToStr(checkpoint: phase0.Checkpoint): string { diff --git a/packages/state-transition/src/block/processBlsToExecutionChange.ts b/packages/state-transition/src/block/processBlsToExecutionChange.ts index 1cc3706a756f..be79f06f3f21 100644 --- a/packages/state-transition/src/block/processBlsToExecutionChange.ts +++ b/packages/state-transition/src/block/processBlsToExecutionChange.ts @@ -1,7 +1,8 @@ -import {toHexString, byteArrayEquals} from "@chainsafe/ssz"; +import {byteArrayEquals} from "@chainsafe/ssz"; import {digest} from "@chainsafe/as-sha256"; import {capella} from "@lodestar/types"; import {BLS_WITHDRAWAL_PREFIX, ETH1_ADDRESS_WITHDRAWAL_PREFIX} from "@lodestar/params"; +import {toHex} from "@lodestar/utils"; import {verifyBlsToExecutionChangeSignature} from "../signatureSets/index.js"; import {CachedBeaconStateCapella} from "../types.js"; @@ -60,9 +61,7 @@ export function isValidBlsToExecutionChange( return { valid: false, error: Error( - `Invalid withdrawalCredentials expected=${toHexString(withdrawalCredentials)} actual=${toHexString( - digestCredentials - )}` + `Invalid withdrawalCredentials expected=${toHex(withdrawalCredentials)} actual=${toHex(digestCredentials)}` ), }; } diff --git a/packages/state-transition/src/block/processConsolidationRequest.ts b/packages/state-transition/src/block/processConsolidationRequest.ts index 71b85e927336..c14612579c58 100644 --- a/packages/state-transition/src/block/processConsolidationRequest.ts +++ b/packages/state-transition/src/block/processConsolidationRequest.ts @@ -3,31 +3,38 @@ import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} import {CachedBeaconStateElectra} from "../types.js"; import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; -import {hasExecutionWithdrawalCredential} from "../util/electra.js"; +import {hasExecutionWithdrawalCredential, switchToCompoundingValidator} from "../util/electra.js"; import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js"; +import {hasEth1WithdrawalCredential} from "../util/capella.js"; +// TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest export function processConsolidationRequest( state: CachedBeaconStateElectra, consolidationRequest: electra.ConsolidationRequest ): void { - // If the pending consolidations queue is full, consolidation requests are ignored - if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) { + const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest; + const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey); + const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey); + + if (sourceIndex === null || targetIndex === null) { return; } - // If there is too little available consolidation churn limit, consolidation requests are ignored - if (getConsolidationChurnLimit(state.epochCtx) <= MIN_ACTIVATION_BALANCE) { + if (isValidSwitchToCompoundRequest(state, consolidationRequest)) { + switchToCompoundingValidator(state, sourceIndex); + // Early return since we have already switched validator to compounding return; } - const {sourcePubkey, targetPubkey} = consolidationRequest; - const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey); - const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey); - - if (sourceIndex === undefined || targetIndex === undefined) { + // If the pending consolidations queue is full, consolidation requests are ignored + if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) { return; } + // If there is too little available consolidation churn limit, consolidation requests are ignored + if (getConsolidationChurnLimit(state.epochCtx) <= MIN_ACTIVATION_BALANCE) { + return; + } // Verify that source != target, so a consolidation cannot be used as an exit. if (sourceIndex === targetIndex) { return; @@ -46,7 +53,7 @@ export function processConsolidationRequest( return; } - if (Buffer.compare(sourceWithdrawalAddress, consolidationRequest.sourceAddress) !== 0) { + if (Buffer.compare(sourceWithdrawalAddress, sourceAddress) !== 0) { return; } @@ -70,4 +77,55 @@ export function processConsolidationRequest( targetIndex, }); state.pendingConsolidations.push(pendingConsolidation); + + // Churn any target excess active balance of target and raise its max + if (hasEth1WithdrawalCredential(targetValidator.withdrawalCredentials)) { + switchToCompoundingValidator(state, targetIndex); + } +} + +/** + * Determine if we should set consolidation target validator to compounding credential + */ +function isValidSwitchToCompoundRequest( + state: CachedBeaconStateElectra, + consolidationRequest: electra.ConsolidationRequest +): boolean { + const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest; + const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey); + const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey); + + // Verify pubkey exists + if (sourceIndex === null) { + return false; + } + + // Switch to compounding requires source and target be equal + if (sourceIndex !== targetIndex) { + return false; + } + + const sourceValidator = state.validators.getReadonly(sourceIndex); + const sourceWithdrawalAddress = sourceValidator.withdrawalCredentials.subarray(12); + // Verify request has been authorized + if (Buffer.compare(sourceWithdrawalAddress, sourceAddress) !== 0) { + return false; + } + + // Verify source withdrawal credentials + if (!hasEth1WithdrawalCredential(sourceValidator.withdrawalCredentials)) { + return false; + } + + // Verify the source is active + if (!isActiveValidator(sourceValidator, state.epochCtx.epoch)) { + return false; + } + + // Verify exit for source has not been initiated + if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH) { + return false; + } + + return true; } diff --git a/packages/state-transition/src/block/processDeposit.ts b/packages/state-transition/src/block/processDeposit.ts index 7e343d7fc33d..b7e9827c4cd7 100644 --- a/packages/state-transition/src/block/processDeposit.ts +++ b/packages/state-transition/src/block/processDeposit.ts @@ -8,6 +8,7 @@ import { EFFECTIVE_BALANCE_INCREMENT, FAR_FUTURE_EPOCH, ForkSeq, + GENESIS_SLOT, MAX_EFFECTIVE_BALANCE, } from "@lodestar/params"; @@ -15,14 +16,7 @@ import {DepositData} from "@lodestar/types/lib/phase0/types.js"; import {DepositRequest} from "@lodestar/types/lib/electra/types.js"; import {BeaconConfig} from "@lodestar/config"; import {ZERO_HASH} from "../constants/index.js"; -import { - computeDomain, - computeSigningRoot, - hasCompoundingWithdrawalCredential, - hasEth1WithdrawalCredential, - increaseBalance, - switchToCompoundingValidator, -} from "../util/index.js"; +import {computeDomain, computeSigningRoot, getMaxEffectiveBalance, increaseBalance} from "../util/index.js"; import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStateElectra} from "../types.js"; /** @@ -61,38 +55,43 @@ export function applyDeposit( state: CachedBeaconStateAllForks, deposit: DepositData | DepositRequest ): void { - const {config, validators, epochCtx} = state; - const {pubkey, withdrawalCredentials, amount} = deposit; + const {config, epochCtx, validators} = state; + const {pubkey, withdrawalCredentials, amount, signature} = deposit; const cachedIndex = epochCtx.getValidatorIndex(pubkey); - if (cachedIndex === undefined || !Number.isSafeInteger(cachedIndex) || cachedIndex >= validators.length) { - if (isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, deposit.signature)) { - addValidatorToRegistry(fork, state, pubkey, withdrawalCredentials, amount); - } - } else { - if (fork < ForkSeq.electra) { + const isNewValidator = cachedIndex === null || !Number.isSafeInteger(cachedIndex) || cachedIndex >= validators.length; + + if (fork < ForkSeq.electra) { + if (isNewValidator) { + if (isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, signature)) { + addValidatorToRegistry(fork, state, pubkey, withdrawalCredentials, amount); + } + } else { // increase balance by deposit amount right away pre-electra increaseBalance(state, cachedIndex, amount); - } else if (fork >= ForkSeq.electra) { - const stateElectra = state as CachedBeaconStateElectra; - const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ - index: cachedIndex, - amount: BigInt(amount), - }); - stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); - - if ( - hasCompoundingWithdrawalCredential(withdrawalCredentials) && - hasEth1WithdrawalCredential(validators.getReadonly(cachedIndex).withdrawalCredentials) && - isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, deposit.signature) - ) { - switchToCompoundingValidator(stateElectra, cachedIndex); + } + } else { + const stateElectra = state as CachedBeaconStateElectra; + const pendingDeposit = ssz.electra.PendingDeposit.toViewDU({ + pubkey, + withdrawalCredentials, + amount, + signature, + slot: GENESIS_SLOT, // Use GENESIS_SLOT to distinguish from a pending deposit request + }); + + if (isNewValidator) { + if (isValidDepositSignature(config, pubkey, withdrawalCredentials, amount, deposit.signature)) { + addValidatorToRegistry(fork, state, pubkey, withdrawalCredentials, 0); + stateElectra.pendingDeposits.push(pendingDeposit); } + } else { + stateElectra.pendingDeposits.push(pendingDeposit); } } } -function addValidatorToRegistry( +export function addValidatorToRegistry( fork: ForkSeq, state: CachedBeaconStateAllForks, pubkey: BLSPubkey, @@ -101,8 +100,10 @@ function addValidatorToRegistry( ): void { const {validators, epochCtx} = state; // add validator and balance entries - const effectiveBalance = - fork < ForkSeq.electra ? Math.min(amount - (amount % EFFECTIVE_BALANCE_INCREMENT), MAX_EFFECTIVE_BALANCE) : 0; + const effectiveBalance = Math.min( + amount - (amount % EFFECTIVE_BALANCE_INCREMENT), + fork < ForkSeq.electra ? MAX_EFFECTIVE_BALANCE : getMaxEffectiveBalance(withdrawalCredentials) + ); validators.push( ssz.phase0.Validator.toViewDU({ pubkey, @@ -138,20 +139,10 @@ function addValidatorToRegistry( stateAltair.currentEpochParticipation.push(0); } - if (fork < ForkSeq.electra) { - state.balances.push(amount); - } else if (fork >= ForkSeq.electra) { - state.balances.push(0); - const stateElectra = state as CachedBeaconStateElectra; - const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ - index: validatorIndex, - amount: BigInt(amount), - }); - stateElectra.pendingBalanceDeposits.push(pendingBalanceDeposit); - } + state.balances.push(amount); } -function isValidDepositSignature( +export function isValidDepositSignature( config: BeaconConfig, pubkey: Uint8Array, withdrawalCredentials: Uint8Array, @@ -173,7 +164,7 @@ function isValidDepositSignature( const signature = Signature.fromBytes(depositSignature, true); return verify(signingRoot, publicKey, signature); - } catch (e) { + } catch (_e) { return false; // Catch all BLS errors: failed key validation, failed signature validation, invalid signature } } diff --git a/packages/state-transition/src/block/processDepositRequest.ts b/packages/state-transition/src/block/processDepositRequest.ts index e5dd99a40c4e..7c6ea4928f54 100644 --- a/packages/state-transition/src/block/processDepositRequest.ts +++ b/packages/state-transition/src/block/processDepositRequest.ts @@ -1,17 +1,20 @@ -import {electra} from "@lodestar/types"; +import {electra, ssz} from "@lodestar/types"; import {ForkSeq, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params"; import {CachedBeaconStateElectra} from "../types.js"; -import {applyDeposit} from "./processDeposit.js"; -export function processDepositRequest( - fork: ForkSeq, - state: CachedBeaconStateElectra, - depositRequest: electra.DepositRequest -): void { +export function processDepositRequest(state: CachedBeaconStateElectra, depositRequest: electra.DepositRequest): void { if (state.depositRequestsStartIndex === UNSET_DEPOSIT_REQUESTS_START_INDEX) { state.depositRequestsStartIndex = BigInt(depositRequest.index); } - applyDeposit(fork, state, depositRequest); + // Create pending deposit + const pendingDeposit = ssz.electra.PendingDeposit.toViewDU({ + pubkey: depositRequest.pubkey, + withdrawalCredentials: depositRequest.withdrawalCredentials, + amount: depositRequest.amount, + signature: depositRequest.signature, + slot: state.slot, + }); + state.pendingDeposits.push(pendingDeposit); } diff --git a/packages/state-transition/src/block/processEth1Data.ts b/packages/state-transition/src/block/processEth1Data.ts index 3d1927744328..92ab147aa772 100644 --- a/packages/state-transition/src/block/processEth1Data.ts +++ b/packages/state-transition/src/block/processEth1Data.ts @@ -58,9 +58,8 @@ export function becomesNewEth1Data( // The +1 is to account for the `eth1Data` supplied to the function. if ((sameVotesCount + 1) * 2 > SLOTS_PER_ETH1_VOTING_PERIOD) { return true; - } else { - return false; } + return false; } function isEqualEth1DataView( diff --git a/packages/state-transition/src/block/processExecutionPayload.ts b/packages/state-transition/src/block/processExecutionPayload.ts index 3c28a400d3bf..3d70e46d40fd 100644 --- a/packages/state-transition/src/block/processExecutionPayload.ts +++ b/packages/state-transition/src/block/processExecutionPayload.ts @@ -1,6 +1,7 @@ -import {toHexString, byteArrayEquals} from "@chainsafe/ssz"; +import {byteArrayEquals} from "@chainsafe/ssz"; import {BeaconBlockBody, BlindedBeaconBlockBody, deneb, isExecutionPayload} from "@lodestar/types"; import {ForkSeq, MAX_BLOBS_PER_BLOCK} from "@lodestar/params"; +import {toHex, toRootHex} from "@lodestar/utils"; import {CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js"; import {getRandaoMix} from "../util/index.js"; import { @@ -23,7 +24,7 @@ export function processExecutionPayload( const {latestExecutionPayloadHeader} = state; if (!byteArrayEquals(payload.parentHash, latestExecutionPayloadHeader.blockHash)) { throw Error( - `Invalid execution payload parentHash ${toHexString(payload.parentHash)} latest blockHash ${toHexString( + `Invalid execution payload parentHash ${toRootHex(payload.parentHash)} latest blockHash ${toRootHex( latestExecutionPayloadHeader.blockHash )}` ); @@ -33,9 +34,7 @@ export function processExecutionPayload( // Verify random const expectedRandom = getRandaoMix(state, state.epochCtx.epoch); if (!byteArrayEquals(payload.prevRandao, expectedRandom)) { - throw Error( - `Invalid execution payload random ${toHexString(payload.prevRandao)} expected=${toHexString(expectedRandom)}` - ); + throw Error(`Invalid execution payload random ${toHex(payload.prevRandao)} expected=${toHex(expectedRandom)}`); } // Verify timestamp diff --git a/packages/state-transition/src/block/processOperations.ts b/packages/state-transition/src/block/processOperations.ts index 6f61e7c242fb..d611581584c1 100644 --- a/packages/state-transition/src/block/processOperations.ts +++ b/packages/state-transition/src/block/processOperations.ts @@ -67,15 +67,15 @@ export function processOperations( const stateElectra = state as CachedBeaconStateElectra; const bodyElectra = body as electra.BeaconBlockBody; - for (const depositRequest of bodyElectra.executionPayload.depositRequests) { - processDepositRequest(fork, stateElectra, depositRequest); + for (const depositRequest of bodyElectra.executionRequests.deposits) { + processDepositRequest(stateElectra, depositRequest); } - for (const elWithdrawalRequest of bodyElectra.executionPayload.withdrawalRequests) { + for (const elWithdrawalRequest of bodyElectra.executionRequests.withdrawals) { processWithdrawalRequest(fork, stateElectra, elWithdrawalRequest); } - for (const elConsolidationRequest of bodyElectra.executionPayload.consolidationRequests) { + for (const elConsolidationRequest of bodyElectra.executionRequests.consolidations) { processConsolidationRequest(stateElectra, elConsolidationRequest); } } diff --git a/packages/state-transition/src/block/processSyncCommittee.ts b/packages/state-transition/src/block/processSyncCommittee.ts index 70c918ed60bf..5f81a984b677 100644 --- a/packages/state-transition/src/block/processSyncCommittee.ts +++ b/packages/state-transition/src/block/processSyncCommittee.ts @@ -96,9 +96,8 @@ export function getSyncCommitteeSignatureSet( // https://github.com/ethereum/eth2.0-specs/blob/30f2a076377264677e27324a8c3c78c590ae5e20/specs/altair/bls.md#eth2_fast_aggregate_verify if (byteArrayEquals(signature, G2_POINT_AT_INFINITY)) { return null; - } else { - throw Error("Empty sync committee signature is not infinity"); } + throw Error("Empty sync committee signature is not infinity"); } const domain = state.config.getDomain(state.slot, DOMAIN_SYNC_COMMITTEE, previousSlot); diff --git a/packages/state-transition/src/block/processWithdrawalRequest.ts b/packages/state-transition/src/block/processWithdrawalRequest.ts index cff1ea03bd80..e8a64ec63e41 100644 --- a/packages/state-transition/src/block/processWithdrawalRequest.ts +++ b/packages/state-transition/src/block/processWithdrawalRequest.ts @@ -1,4 +1,3 @@ -import {toHexString} from "@chainsafe/ssz"; import {electra, phase0, ssz} from "@lodestar/types"; import { FAR_FUTURE_EPOCH, @@ -8,6 +7,7 @@ import { ForkSeq, } from "@lodestar/params"; +import {toHex} from "@lodestar/utils"; import {CachedBeaconStateElectra} from "../types.js"; import {hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential} from "../util/electra.js"; import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js"; @@ -33,7 +33,7 @@ export function processWithdrawalRequest( // bail out if validator is not in beacon state // note that we don't need to check for 6110 unfinalized vals as they won't be eligible for withdraw/exit anyway const validatorIndex = pubkey2index.get(withdrawalRequest.validatorPubkey); - if (validatorIndex === undefined) { + if (validatorIndex === null) { return; } @@ -85,8 +85,8 @@ function isValidatorEligibleForWithdrawOrExit( state: CachedBeaconStateElectra ): boolean { const {withdrawalCredentials} = validator; - const addressStr = toHexString(withdrawalCredentials.subarray(12)); - const sourceAddressStr = toHexString(sourceAddress); + const addressStr = toHex(withdrawalCredentials.subarray(12)); + const sourceAddressStr = toHex(sourceAddress); const {epoch: currentEpoch, config} = state.epochCtx; return ( diff --git a/packages/state-transition/src/block/processWithdrawals.ts b/packages/state-transition/src/block/processWithdrawals.ts index d4dfd47b4d94..610a2ed62b41 100644 --- a/packages/state-transition/src/block/processWithdrawals.ts +++ b/packages/state-transition/src/block/processWithdrawals.ts @@ -99,6 +99,8 @@ export function getExpectedWithdrawals( const withdrawals: capella.Withdrawal[] = []; const isPostElectra = fork >= ForkSeq.electra; + // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) + let partialWithdrawalsCount = 0; if (isPostElectra) { const stateElectra = state as CachedBeaconStateElectra; @@ -138,11 +140,10 @@ export function getExpectedWithdrawals( }); withdrawalIndex++; } + partialWithdrawalsCount++; } } - // partialWithdrawalsCount is withdrawals coming from EL since electra (EIP-7002) - const partialWithdrawalsCount = withdrawals.length; const bound = Math.min(validators.length, MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP); let n = 0; // Just run a bounded loop max iterating over all withdrawals diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index af6e976e9089..66ce12b82d18 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -1,6 +1,6 @@ import {PublicKey} from "@chainsafe/blst"; import * as immutable from "immutable"; -import {fromHexString} from "@chainsafe/ssz"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import { BLSSignature, CommitteeIndex, @@ -8,6 +8,7 @@ import { Slot, ValidatorIndex, phase0, + RootHex, SyncPeriod, Attestation, IndexedAttestation, @@ -25,7 +26,7 @@ import { SLOTS_PER_EPOCH, WEIGHT_DENOMINATOR, } from "@lodestar/params"; -import {LodestarError} from "@lodestar/utils"; +import {LodestarError, fromHex} from "@lodestar/utils"; import { computeActivationExitEpoch, computeEpochAtSlot, @@ -38,28 +39,34 @@ import { computeProposers, getActivationChurnLimit, } from "../util/index.js"; -import {computeEpochShuffling, EpochShuffling, getShufflingDecisionBlock} from "../util/epochShuffling.js"; +import { + computeEpochShuffling, + EpochShuffling, + calculateShufflingDecisionRoot, + IShufflingCache, +} from "../util/epochShuffling.js"; import {computeBaseRewardPerIncrement, computeSyncParticipantReward} from "../util/syncCommittee.js"; import {sumTargetUnslashedBalanceIncrements} from "../util/targetUnslashedBalance.js"; import {getTotalSlashingsByIncrement} from "../epoch/processSlashings.js"; +import {AttesterDuty, calculateCommitteeAssignments} from "../util/calculateCommitteeAssignments.js"; import {EpochCacheMetrics} from "../metrics.js"; import {EffectiveBalanceIncrements, getEffectiveBalanceIncrementsWithLen} from "./effectiveBalanceIncrements.js"; +import {BeaconStateAllForks, BeaconStateAltair} from "./types.js"; import { Index2PubkeyCache, - PubkeyIndexMap, UnfinalizedPubkeyIndexMap, syncPubkeys, toMemoryEfficientHexStr, PubkeyHex, newUnfinalizedPubkeyIndexMap, } from "./pubkeyCache.js"; -import {BeaconStateAllForks, BeaconStateAltair, ShufflingGetter} from "./types.js"; import { computeSyncCommitteeCache, getSyncCommitteeCache, SyncCommitteeCache, SyncCommitteeCacheEmpty, } from "./syncCommitteeCache.js"; +import {CachedBeaconStateAllForks} from "./stateCache.js"; /** `= PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)` */ export const PROPOSER_WEIGHT_FACTOR = PROPOSER_WEIGHT / (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT); @@ -68,12 +75,12 @@ export type EpochCacheImmutableData = { config: BeaconConfig; pubkey2index: PubkeyIndexMap; index2pubkey: Index2PubkeyCache; + shufflingCache?: IShufflingCache; }; export type EpochCacheOpts = { skipSyncCommitteeCache?: boolean; skipSyncPubkeys?: boolean; - shufflingGetter?: ShufflingGetter; }; /** Defers computing proposers by persisting only the seed, and dropping it once indexes are computed */ @@ -128,6 +135,11 @@ export class EpochCache { * Unique pubkey registry shared in the same fork. There should only exist one for the fork. */ unfinalizedPubkey2index: UnfinalizedPubkeyIndexMap; + /** + * ShufflingCache is passed in from `beacon-node` so should be available at runtime but may not be + * present during testing. + */ + shufflingCache?: IShufflingCache; /** * Indexes of the block proposers for the current epoch. @@ -146,6 +158,12 @@ export class EpochCache { */ proposersNextEpoch: ProposersDeferred; + /** + * Epoch decision roots to look up correct shuffling from the Shuffling Cache + */ + previousDecisionRoot: RootHex; + currentDecisionRoot: RootHex; + nextDecisionRoot: RootHex; /** * Shuffling of validator indexes. Immutable through the epoch, then it's replaced entirely. * Note: Per spec definition, shuffling will always be defined. They are never called before loadState() @@ -156,7 +174,12 @@ export class EpochCache { /** Same as previousShuffling */ currentShuffling: EpochShuffling; /** Same as previousShuffling */ - nextShuffling: EpochShuffling; + nextShuffling: EpochShuffling | null; + /** + * Cache nextActiveIndices so that in afterProcessEpoch the next shuffling can be build synchronously + * in case it is not built or the ShufflingCache is not available + */ + nextActiveIndices: Uint32Array; /** * Effective balances, for altair processAttestations() */ @@ -228,7 +251,6 @@ export class EpochCache { nextSyncCommitteeIndexed: SyncCommitteeCache; // TODO: Helper stats - epoch: Epoch; syncPeriod: SyncPeriod; /** * state.validators.length of every state at epoch boundary @@ -240,17 +262,28 @@ export class EpochCache { */ historicalValidatorLengths: immutable.List; + epoch: Epoch; + + get nextEpoch(): Epoch { + return this.epoch + 1; + } + constructor(data: { config: BeaconConfig; pubkey2index: PubkeyIndexMap; index2pubkey: Index2PubkeyCache; unfinalizedPubkey2index: UnfinalizedPubkeyIndexMap; + shufflingCache?: IShufflingCache; proposers: number[]; proposersPrevEpoch: number[] | null; proposersNextEpoch: ProposersDeferred; + previousDecisionRoot: RootHex; + currentDecisionRoot: RootHex; + nextDecisionRoot: RootHex; previousShuffling: EpochShuffling; currentShuffling: EpochShuffling; - nextShuffling: EpochShuffling; + nextShuffling: EpochShuffling | null; + nextActiveIndices: Uint32Array; effectiveBalanceIncrements: EffectiveBalanceIncrements; totalSlashingsByIncrement: number; syncParticipantReward: number; @@ -273,12 +306,17 @@ export class EpochCache { this.pubkey2index = data.pubkey2index; this.index2pubkey = data.index2pubkey; this.unfinalizedPubkey2index = data.unfinalizedPubkey2index; + this.shufflingCache = data.shufflingCache; this.proposers = data.proposers; this.proposersPrevEpoch = data.proposersPrevEpoch; this.proposersNextEpoch = data.proposersNextEpoch; + this.previousDecisionRoot = data.previousDecisionRoot; + this.currentDecisionRoot = data.currentDecisionRoot; + this.nextDecisionRoot = data.nextDecisionRoot; this.previousShuffling = data.previousShuffling; this.currentShuffling = data.currentShuffling; this.nextShuffling = data.nextShuffling; + this.nextActiveIndices = data.nextActiveIndices; this.effectiveBalanceIncrements = data.effectiveBalanceIncrements; this.totalSlashingsByIncrement = data.totalSlashingsByIncrement; this.syncParticipantReward = data.syncParticipantReward; @@ -306,7 +344,7 @@ export class EpochCache { */ static createFromState( state: BeaconStateAllForks, - {config, pubkey2index, index2pubkey}: EpochCacheImmutableData, + {config, pubkey2index, index2pubkey, shufflingCache}: EpochCacheImmutableData, opts?: EpochCacheOpts ): EpochCache { const currentEpoch = computeEpochAtSlot(state.slot); @@ -329,18 +367,18 @@ export class EpochCache { const effectiveBalanceIncrements = getEffectiveBalanceIncrementsWithLen(validatorCount); const totalSlashingsByIncrement = getTotalSlashingsByIncrement(state); - const previousActiveIndices: ValidatorIndex[] = []; - const currentActiveIndices: ValidatorIndex[] = []; - const nextActiveIndices: ValidatorIndex[] = []; + const previousActiveIndicesAsNumberArray: ValidatorIndex[] = []; + const currentActiveIndicesAsNumberArray: ValidatorIndex[] = []; + const nextActiveIndicesAsNumberArray: ValidatorIndex[] = []; // BeaconChain could provide a shuffling cache to avoid re-computing shuffling every epoch // in that case, we don't need to compute shufflings again - const previousShufflingDecisionBlock = getShufflingDecisionBlock(state, previousEpoch); - const cachedPreviousShuffling = opts?.shufflingGetter?.(previousEpoch, previousShufflingDecisionBlock); - const currentShufflingDecisionBlock = getShufflingDecisionBlock(state, currentEpoch); - const cachedCurrentShuffling = opts?.shufflingGetter?.(currentEpoch, currentShufflingDecisionBlock); - const nextShufflingDecisionBlock = getShufflingDecisionBlock(state, nextEpoch); - const cachedNextShuffling = opts?.shufflingGetter?.(nextEpoch, nextShufflingDecisionBlock); + const previousDecisionRoot = calculateShufflingDecisionRoot(config, state, previousEpoch); + const cachedPreviousShuffling = shufflingCache?.getSync(previousEpoch, previousDecisionRoot); + const currentDecisionRoot = calculateShufflingDecisionRoot(config, state, currentEpoch); + const cachedCurrentShuffling = shufflingCache?.getSync(currentEpoch, currentDecisionRoot); + const nextDecisionRoot = calculateShufflingDecisionRoot(config, state, nextEpoch); + const cachedNextShuffling = shufflingCache?.getSync(nextEpoch, nextDecisionRoot); for (let i = 0; i < validatorCount; i++) { const validator = validators[i]; @@ -351,17 +389,17 @@ export class EpochCache { // we only need to track active indices for previous, current and next epoch if we have to compute shufflings // skip doing that if we already have cached shufflings if (cachedPreviousShuffling == null && isActiveValidator(validator, previousEpoch)) { - previousActiveIndices.push(i); + previousActiveIndicesAsNumberArray.push(i); } if (isActiveValidator(validator, currentEpoch)) { if (cachedCurrentShuffling == null) { - currentActiveIndices.push(i); + currentActiveIndicesAsNumberArray.push(i); } // We track totalActiveBalanceIncrements as ETH to fit total network balance in a JS number (53 bits) totalActiveBalanceIncrements += effectiveBalanceIncrements[i]; } if (cachedNextShuffling == null && isActiveValidator(validator, nextEpoch)) { - nextActiveIndices.push(i); + nextActiveIndicesAsNumberArray.push(i); } const {exitEpoch} = validator; @@ -383,16 +421,48 @@ export class EpochCache { throw Error("totalActiveBalanceIncrements >= Number.MAX_SAFE_INTEGER. MAX_EFFECTIVE_BALANCE is too low."); } - const currentShuffling = - cachedCurrentShuffling ?? - computeEpochShuffling(state, currentActiveIndices, currentActiveIndices.length, currentEpoch); - const previousShuffling = - cachedPreviousShuffling ?? - (isGenesis - ? currentShuffling - : computeEpochShuffling(state, previousActiveIndices, previousActiveIndices.length, previousEpoch)); - const nextShuffling = - cachedNextShuffling ?? computeEpochShuffling(state, nextActiveIndices, nextActiveIndices.length, nextEpoch); + const nextActiveIndices = new Uint32Array(nextActiveIndicesAsNumberArray); + let previousShuffling: EpochShuffling; + let currentShuffling: EpochShuffling; + let nextShuffling: EpochShuffling; + + if (!shufflingCache) { + // Only for testing. shufflingCache should always be available in prod + previousShuffling = computeEpochShuffling( + state, + new Uint32Array(previousActiveIndicesAsNumberArray), + previousEpoch + ); + + currentShuffling = isGenesis + ? previousShuffling + : computeEpochShuffling(state, new Uint32Array(currentActiveIndicesAsNumberArray), currentEpoch); + + nextShuffling = computeEpochShuffling(state, nextActiveIndices, nextEpoch); + } else { + currentShuffling = cachedCurrentShuffling + ? cachedCurrentShuffling + : shufflingCache.getSync(currentEpoch, currentDecisionRoot, { + state, + activeIndices: new Uint32Array(currentActiveIndicesAsNumberArray), + }); + + previousShuffling = cachedPreviousShuffling + ? cachedPreviousShuffling + : isGenesis + ? currentShuffling + : shufflingCache.getSync(previousEpoch, previousDecisionRoot, { + state, + activeIndices: new Uint32Array(previousActiveIndicesAsNumberArray), + }); + + nextShuffling = cachedNextShuffling + ? cachedNextShuffling + : shufflingCache.getSync(nextEpoch, nextDecisionRoot, { + state, + activeIndices: nextActiveIndices, + }); + } const currentProposerSeed = getSeed(state, currentEpoch, DOMAIN_BEACON_PROPOSER); @@ -483,13 +553,18 @@ export class EpochCache { index2pubkey, // `createFromFinalizedState()` creates cache with empty unfinalizedPubkey2index. Be cautious to only pass in finalized state unfinalizedPubkey2index: newUnfinalizedPubkeyIndexMap(), + shufflingCache, proposers, // On first epoch, set to null to prevent unnecessary work since this is only used for metrics proposersPrevEpoch: null, proposersNextEpoch, + previousDecisionRoot, + currentDecisionRoot, + nextDecisionRoot, previousShuffling, currentShuffling, nextShuffling, + nextActiveIndices, effectiveBalanceIncrements, totalSlashingsByIncrement, syncParticipantReward, @@ -524,13 +599,18 @@ export class EpochCache { index2pubkey: this.index2pubkey, // No need to clone this reference. On each mutation the `unfinalizedPubkey2index` reference is replaced, @see `addPubkey` unfinalizedPubkey2index: this.unfinalizedPubkey2index, + shufflingCache: this.shufflingCache, // Immutable data proposers: this.proposers, proposersPrevEpoch: this.proposersPrevEpoch, proposersNextEpoch: this.proposersNextEpoch, + previousDecisionRoot: this.previousDecisionRoot, + currentDecisionRoot: this.currentDecisionRoot, + nextDecisionRoot: this.nextDecisionRoot, previousShuffling: this.previousShuffling, currentShuffling: this.currentShuffling, nextShuffling: this.nextShuffling, + nextActiveIndices: this.nextActiveIndices, // Uint8Array, requires cloning, but it is cloned only when necessary before an epoch transition // See EpochCache.beforeEpochTransition() effectiveBalanceIncrements: this.effectiveBalanceIncrements, @@ -556,46 +636,98 @@ export class EpochCache { /** * Called to re-use information, such as the shuffling of the next epoch, after transitioning into a - * new epoch. + * new epoch. Also handles pre-computation of values that may change during the upcoming epoch and + * that get used in the following epoch transition. Often those pre-computations are not used by the + * chain but are courtesy values that are served via the API for epoch look ahead of duties. + * + * Steps for afterProcessEpoch + * 1) update previous/current/next values of cached items */ afterProcessEpoch( - state: BeaconStateAllForks, + state: CachedBeaconStateAllForks, epochTransitionCache: { - indicesEligibleForActivationQueue: ValidatorIndex[]; - nextEpochShufflingActiveValidatorIndices: ValidatorIndex[]; - nextEpochShufflingActiveIndicesLength: number; + nextShufflingDecisionRoot: RootHex; + nextShufflingActiveIndices: Uint32Array; nextEpochTotalActiveBalanceByIncrement: number; } ): void { + // Because the slot was incremented before entering this function the "next epoch" is actually the "current epoch" + // in this context but that is not actually true because the state transition happens in the last 4 seconds of the + // epoch. For the context of this function "upcoming epoch" is used to denote the epoch that will begin after this + // function returns. The epoch that is "next" once the state transition is complete is referred to as the + // epochAfterUpcoming for the same reason to help minimize confusion. + const upcomingEpoch = this.nextEpoch; + const epochAfterUpcoming = upcomingEpoch + 1; + + // move current to previous this.previousShuffling = this.currentShuffling; - this.currentShuffling = this.nextShuffling; - const currEpoch = this.currentShuffling.epoch; - const nextEpoch = currEpoch + 1; - - this.nextShuffling = computeEpochShuffling( - state, - epochTransitionCache.nextEpochShufflingActiveValidatorIndices, - epochTransitionCache.nextEpochShufflingActiveIndicesLength, - nextEpoch - ); - - // Roll current proposers into previous proposers for metrics + this.previousDecisionRoot = this.currentDecisionRoot; this.proposersPrevEpoch = this.proposers; - const currentProposerSeed = getSeed(state, this.currentShuffling.epoch, DOMAIN_BEACON_PROPOSER); + // move next to current or calculate upcoming + this.currentDecisionRoot = this.nextDecisionRoot; + if (this.nextShuffling) { + // was already pulled from the ShufflingCache to the EpochCache (should be in most cases) + this.currentShuffling = this.nextShuffling; + } else { + this.shufflingCache?.metrics?.shufflingCache.nextShufflingNotOnEpochCache.inc(); + this.currentShuffling = + this.shufflingCache?.getSync(upcomingEpoch, this.currentDecisionRoot, { + state, + // have to use the "nextActiveIndices" that were saved in the last transition here to calculate + // the upcoming shuffling if it is not already built (similar condition to the below computation) + activeIndices: this.nextActiveIndices, + }) ?? + // allow for this case during testing where the ShufflingCache is not present, may affect perf testing + // so should be taken into account when structuring tests. Should not affect unit or other tests though + computeEpochShuffling(state, this.nextActiveIndices, upcomingEpoch); + } + const upcomingProposerSeed = getSeed(state, upcomingEpoch, DOMAIN_BEACON_PROPOSER); + // next epoch was moved to current epoch so use current here this.proposers = computeProposers( - this.config.getForkSeqAtEpoch(currEpoch), - currentProposerSeed, + this.config.getForkSeqAtEpoch(upcomingEpoch), + upcomingProposerSeed, this.currentShuffling, this.effectiveBalanceIncrements ); + // handle next values + this.nextDecisionRoot = epochTransitionCache.nextShufflingDecisionRoot; + this.nextActiveIndices = epochTransitionCache.nextShufflingActiveIndices; + if (this.shufflingCache) { + this.nextShuffling = null; + // This promise will resolve immediately after the synchronous code of the state-transition runs. Until + // the build is done on a worker thread it will be calculated immediately after the epoch transition + // completes. Once the work is done concurrently it should be ready by time this get runs so the promise + // will resolve directly on the next spin of the event loop because the epoch transition and shuffling take + // about the same time to calculate so theoretically its ready now. Do not await here though in case it + // is not ready yet as the transition must not be asynchronous. + this.shufflingCache + .get(epochAfterUpcoming, this.nextDecisionRoot) + .then((shuffling) => { + if (!shuffling) { + throw new Error("EpochShuffling not returned from get in afterProcessEpoch"); + } + this.nextShuffling = shuffling; + }) + .catch((err) => { + this.shufflingCache?.logger?.error( + "EPOCH_CONTEXT_SHUFFLING_BUILD_ERROR", + {epoch: epochAfterUpcoming, decisionRoot: epochTransitionCache.nextShufflingDecisionRoot}, + err + ); + }); + } else { + // Only for testing. shufflingCache should always be available in prod + this.nextShuffling = computeEpochShuffling(state, this.nextActiveIndices, epochAfterUpcoming); + } + // Only pre-compute the seed since it's very cheap. Do the expensive computeProposers() call only on demand. - this.proposersNextEpoch = {computed: false, seed: getSeed(state, this.nextShuffling.epoch, DOMAIN_BEACON_PROPOSER)}; + this.proposersNextEpoch = {computed: false, seed: getSeed(state, epochAfterUpcoming, DOMAIN_BEACON_PROPOSER)}; // TODO: DEDUPLICATE from createEpochCache // - // Precompute churnLimit for efficient initiateValidatorExit() during block proposing MUST be recompute everytime the + // Precompute churnLimit for efficient initiateValidatorExit() during block proposing MUST be recompute every time the // active validator indices set changes in size. Validators change active status only when: // - validator.activation_epoch is set. Only changes in process_registry_updates() if validator can be activated. If // the value changes it will be set to `epoch + 1 + MAX_SEED_LOOKAHEAD`. @@ -617,14 +749,14 @@ export class EpochCache { ); // Maybe advance exitQueueEpoch at the end of the epoch if there haven't been any exists for a while - const exitQueueEpoch = computeActivationExitEpoch(currEpoch); + const exitQueueEpoch = computeActivationExitEpoch(upcomingEpoch); if (exitQueueEpoch > this.exitQueueEpoch) { this.exitQueueEpoch = exitQueueEpoch; this.exitQueueChurn = 0; } this.totalActiveBalanceIncrements = epochTransitionCache.nextEpochTotalActiveBalanceByIncrement; - if (currEpoch >= this.config.ALTAIR_FORK_EPOCH) { + if (upcomingEpoch >= this.config.ALTAIR_FORK_EPOCH) { this.syncParticipantReward = computeSyncParticipantReward(this.totalActiveBalanceIncrements); this.syncProposerReward = Math.floor(this.syncParticipantReward * PROPOSER_WEIGHT_FACTOR); this.baseRewardPerIncrement = computeBaseRewardPerIncrement(this.totalActiveBalanceIncrements); @@ -645,7 +777,7 @@ export class EpochCache { // Only keep validatorLength for epochs after finalized cpState.epoch // eg. [100(epoch 1), 102(epoch 2)].push(104(epoch 3)), this.epoch = 3, finalized cp epoch = 1 // We keep the last (3 - 1) items = [102, 104] - if (currEpoch >= this.config.ELECTRA_FORK_EPOCH) { + if (upcomingEpoch >= this.config.ELECTRA_FORK_EPOCH) { this.historicalValidatorLengths = this.historicalValidatorLengths.push(state.validators.length); // If number of validatorLengths we want to keep exceeds the current list size, it implies @@ -745,6 +877,10 @@ export class EpochCache { return this.proposers; } + getBeaconProposersPrevEpoch(): ValidatorIndex[] | null { + return this.proposersPrevEpoch; + } + /** * We allow requesting proposal duties 1 epoch in the future as in normal network conditions it's possible to predict * the correct shuffling with high probability. While knowing the proposers in advance is not useful for consensus, @@ -785,9 +921,9 @@ export class EpochCache { getBeaconProposersNextEpoch(): ValidatorIndex[] { if (!this.proposersNextEpoch.computed) { const indexes = computeProposers( - this.config.getForkSeqAtEpoch(this.epoch + 1), + this.config.getForkSeqAtEpoch(this.nextEpoch), this.proposersNextEpoch.seed, - this.nextShuffling, + this.getShufflingAtEpoch(this.nextEpoch), this.effectiveBalanceIncrements ); this.proposersNextEpoch = {computed: true, indexes}; @@ -821,50 +957,27 @@ export class EpochCache { const validatorIndices = this.getBeaconCommittee(data.slot, data.index); return aggregationBits.intersectValues(validatorIndices); - } else { - const {aggregationBits, committeeBits, data} = attestation as electra.Attestation; + } + const {aggregationBits, committeeBits, data} = attestation as electra.Attestation; - // There is a naming conflict on the term `committeeIndices` - // In Lodestar it usually means a list of validator indices of participants in a committee - // In the spec it means a list of committee indices according to committeeBits - // This `committeeIndices` refers to the latter - // TODO Electra: resolve the naming conflicts - const committeeIndices = committeeBits.getTrueBitIndexes(); + // There is a naming conflict on the term `committeeIndices` + // In Lodestar it usually means a list of validator indices of participants in a committee + // In the spec it means a list of committee indices according to committeeBits + // This `committeeIndices` refers to the latter + // TODO Electra: resolve the naming conflicts + const committeeIndices = committeeBits.getTrueBitIndexes(); - const validatorIndices = this.getBeaconCommittees(data.slot, committeeIndices); + const validatorIndices = this.getBeaconCommittees(data.slot, committeeIndices); - return aggregationBits.intersectValues(validatorIndices); - } + return aggregationBits.intersectValues(validatorIndices); } getCommitteeAssignments( epoch: Epoch, requestedValidatorIndices: ValidatorIndex[] ): Map { - const requestedValidatorIndicesSet = new Set(requestedValidatorIndices); - const duties = new Map(); - - const epochCommittees = this.getShufflingAtEpoch(epoch).committees; - for (let epochSlot = 0; epochSlot < SLOTS_PER_EPOCH; epochSlot++) { - const slotCommittees = epochCommittees[epochSlot]; - for (let i = 0, committeesAtSlot = slotCommittees.length; i < committeesAtSlot; i++) { - for (let j = 0, committeeLength = slotCommittees[i].length; j < committeeLength; j++) { - const validatorIndex = slotCommittees[i][j]; - if (requestedValidatorIndicesSet.has(validatorIndex)) { - duties.set(validatorIndex, { - validatorIndex, - committeeLength, - committeesAtSlot, - validatorCommitteeIndex: j, - committeeIndex: i, - slot: epoch * SLOTS_PER_EPOCH + epochSlot, - }); - } - } - } - } - - return duties; + const shuffling = this.getShufflingAtEpoch(epoch); + return calculateCommitteeAssignments(shuffling, requestedValidatorIndices); } /** @@ -913,12 +1026,11 @@ export class EpochCache { return this.index2pubkey[index]; } - getValidatorIndex(pubkey: Uint8Array | PubkeyHex): ValidatorIndex | undefined { + getValidatorIndex(pubkey: Uint8Array): ValidatorIndex | null { if (this.isPostElectra()) { - return this.pubkey2index.get(pubkey) ?? this.unfinalizedPubkey2index.get(toMemoryEfficientHexStr(pubkey)); - } else { - return this.pubkey2index.get(pubkey); + return this.pubkey2index.get(pubkey) ?? this.unfinalizedPubkey2index.get(toMemoryEfficientHexStr(pubkey)) ?? null; } + return this.pubkey2index.get(pubkey); } /** @@ -949,22 +1061,24 @@ export class EpochCache { * Add finalized validator index and pubkey into finalized cache. * Since addFinalizedPubkey() primarily takes pubkeys from unfinalized cache, it can take pubkey hex string directly */ - addFinalizedPubkey(index: ValidatorIndex, pubkey: PubkeyHex | Uint8Array, metrics?: EpochCacheMetrics): void { + addFinalizedPubkey(index: ValidatorIndex, pubkeyOrHex: PubkeyHex | Uint8Array, metrics?: EpochCacheMetrics): void { + const pubkey = typeof pubkeyOrHex === "string" ? fromHex(pubkeyOrHex) : pubkeyOrHex; const existingIndex = this.pubkey2index.get(pubkey); - if (existingIndex !== undefined) { + if (existingIndex !== null) { if (existingIndex === index) { // Repeated insert. metrics?.finalizedPubkeyDuplicateInsert.inc(); return; - } else { - // attempt to insert the same pubkey with different index, should never happen. - throw Error("inserted existing pubkey into finalizedPubkey2index cache with a different index"); } + // attempt to insert the same pubkey with different index, should never happen. + throw Error( + `inserted existing pubkey into finalizedPubkey2index cache with a different index, index=${index} priorIndex=${existingIndex}` + ); } this.pubkey2index.set(pubkey, index); - const pubkeyBytes = pubkey instanceof Uint8Array ? pubkey : fromHexString(pubkey); + const pubkeyBytes = pubkey instanceof Uint8Array ? pubkey : fromHex(pubkey); this.index2pubkey[index] = PublicKey.fromBytes(pubkeyBytes); // Optimize for aggregation } @@ -988,6 +1102,13 @@ export class EpochCache { getShufflingAtEpoch(epoch: Epoch): EpochShuffling { const shuffling = this.getShufflingAtEpochOrNull(epoch); if (shuffling === null) { + if (epoch === this.nextEpoch) { + throw new EpochCacheError({ + code: EpochCacheErrorCode.NEXT_SHUFFLING_NOT_AVAILABLE, + epoch: epoch, + decisionRoot: this.getShufflingDecisionRoot(this.nextEpoch), + }); + } throw new EpochCacheError({ code: EpochCacheErrorCode.COMMITTEE_EPOCH_OUT_OF_RANGE, currentEpoch: this.currentShuffling.epoch, @@ -998,15 +1119,37 @@ export class EpochCache { return shuffling; } + getShufflingDecisionRoot(epoch: Epoch): RootHex { + switch (epoch) { + case this.epoch - 1: + return this.previousDecisionRoot; + case this.epoch: + return this.currentDecisionRoot; + case this.nextEpoch: + return this.nextDecisionRoot; + default: + throw new EpochCacheError({ + code: EpochCacheErrorCode.DECISION_ROOT_EPOCH_OUT_OF_RANGE, + currentEpoch: this.epoch, + requestedEpoch: epoch, + }); + } + } + getShufflingAtEpochOrNull(epoch: Epoch): EpochShuffling | null { - if (epoch === this.previousShuffling.epoch) { - return this.previousShuffling; - } else if (epoch === this.currentShuffling.epoch) { - return this.currentShuffling; - } else if (epoch === this.nextShuffling.epoch) { - return this.nextShuffling; - } else { - return null; + switch (epoch) { + case this.epoch - 1: + return this.previousShuffling; + case this.epoch: + return this.currentShuffling; + case this.nextEpoch: + if (!this.nextShuffling) { + this.nextShuffling = + this.shufflingCache?.getSync(this.nextEpoch, this.getShufflingDecisionRoot(this.nextEpoch)) ?? null; + } + return this.nextShuffling; + default: + return null; } } @@ -1103,19 +1246,11 @@ function getEffectiveBalanceIncrementsByteLen(validatorCount: number): number { return 1024 * Math.ceil(validatorCount / 1024); } -// Copied from lodestar-api package to avoid depending on the package -type AttesterDuty = { - validatorIndex: ValidatorIndex; - committeeIndex: CommitteeIndex; - committeeLength: number; - committeesAtSlot: number; - validatorCommitteeIndex: number; - slot: Slot; -}; - export enum EpochCacheErrorCode { COMMITTEE_INDEX_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_COMMITTEE_INDEX_OUT_OF_RANGE", COMMITTEE_EPOCH_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_COMMITTEE_EPOCH_OUT_OF_RANGE", + DECISION_ROOT_EPOCH_OUT_OF_RANGE = "EPOCH_CONTEXT_ERROR_DECISION_ROOT_EPOCH_OUT_OF_RANGE", + NEXT_SHUFFLING_NOT_AVAILABLE = "EPOCH_CONTEXT_ERROR_NEXT_SHUFFLING_NOT_AVAILABLE", NO_SYNC_COMMITTEE = "EPOCH_CONTEXT_ERROR_NO_SYNC_COMMITTEE", PROPOSER_EPOCH_MISMATCH = "EPOCH_CONTEXT_ERROR_PROPOSER_EPOCH_MISMATCH", } @@ -1131,6 +1266,16 @@ type EpochCacheErrorType = requestedEpoch: Epoch; currentEpoch: Epoch; } + | { + code: EpochCacheErrorCode.DECISION_ROOT_EPOCH_OUT_OF_RANGE; + requestedEpoch: Epoch; + currentEpoch: Epoch; + } + | { + code: EpochCacheErrorCode.NEXT_SHUFFLING_NOT_AVAILABLE; + epoch: Epoch; + decisionRoot: RootHex; + } | { code: EpochCacheErrorCode.NO_SYNC_COMMITTEE; epoch: Epoch; diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index 6f27ad96d1c8..2a35317fbc93 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -1,6 +1,12 @@ -import {Epoch, ValidatorIndex, phase0} from "@lodestar/types"; -import {intDiv} from "@lodestar/utils"; -import {EPOCHS_PER_SLASHINGS_VECTOR, FAR_FUTURE_EPOCH, ForkSeq, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; +import {phase0, Epoch, RootHex, ValidatorIndex} from "@lodestar/types"; +import {intDiv, toRootHex} from "@lodestar/utils"; +import { + EPOCHS_PER_SLASHINGS_VECTOR, + FAR_FUTURE_EPOCH, + ForkSeq, + SLOTS_PER_HISTORICAL_ROOT, + MIN_ACTIVATION_BALANCE, +} from "@lodestar/params"; import { hasMarkers, @@ -133,12 +139,6 @@ export interface EpochTransitionCache { */ validators: phase0.Validator[]; - /** - * This is for electra only - * Validators that're switched to compounding during processPendingConsolidations(), not available in beforeProcessEpoch() - */ - newCompoundingValidators?: Set; - /** * balances array will be populated by processRewardsAndPenalties() and consumed by processEffectiveBalanceUpdates(). * processRewardsAndPenalties() already has a regular Javascript array of balances. @@ -155,12 +155,12 @@ export interface EpochTransitionCache { * | beforeProcessEpoch | calculate during the validator loop| * | afterEpochTransitionCache | read it | */ - nextEpochShufflingActiveValidatorIndices: ValidatorIndex[]; + nextShufflingActiveIndices: Uint32Array; /** - * We do not use up to `nextEpochShufflingActiveValidatorIndices.length`, use this to control that + * Shuffling decision root that gets set on the EpochCache in afterProcessEpoch */ - nextEpochShufflingActiveIndicesLength: number; + nextShufflingDecisionRoot: RootHex; /** * Altair specific, this is total active balances for the next epoch. @@ -360,6 +360,24 @@ export function beforeProcessEpoch( } } + // Trigger async build of shuffling for epoch after next (nextShuffling post epoch transition) + const epochAfterNext = state.epochCtx.nextEpoch + 1; + // cannot call calculateShufflingDecisionRoot here because spec prevent getting current slot + // as a decision block. we are part way through the transition though and this was added in + // process slot beforeProcessEpoch happens so it available and valid + const nextShufflingDecisionRoot = toRootHex(state.blockRoots.get(state.slot % SLOTS_PER_HISTORICAL_ROOT)); + const nextShufflingActiveIndices = new Uint32Array(nextEpochShufflingActiveIndicesLength); + if (nextEpochShufflingActiveIndicesLength > nextEpochShufflingActiveValidatorIndices.length) { + throw new Error( + `Invalid activeValidatorCount: ${nextEpochShufflingActiveIndicesLength} > ${nextEpochShufflingActiveValidatorIndices.length}` + ); + } + // only the first `activeValidatorCount` elements are copied to `activeIndices` + for (let i = 0; i < nextEpochShufflingActiveIndicesLength; i++) { + nextShufflingActiveIndices[i] = nextEpochShufflingActiveValidatorIndices[i]; + } + state.epochCtx.shufflingCache?.build(epochAfterNext, nextShufflingDecisionRoot, state, nextShufflingActiveIndices); + if (totalActiveStakeByIncrement < 1) { totalActiveStakeByIncrement = 1; } else if (totalActiveStakeByIncrement >= Number.MAX_SAFE_INTEGER) { @@ -483,8 +501,8 @@ export function beforeProcessEpoch( indicesEligibleForActivationQueue, indicesEligibleForActivation, indicesToEject, - nextEpochShufflingActiveValidatorIndices, - nextEpochShufflingActiveIndicesLength, + nextShufflingDecisionRoot, + nextShufflingActiveIndices, // to be updated in processEffectiveBalanceUpdates nextEpochTotalActiveBalanceByIncrement: 0, isActivePrevEpoch, @@ -494,8 +512,6 @@ export function beforeProcessEpoch( inclusionDelays, flags, validators, - // will be assigned in processPendingConsolidations() - newCompoundingValidators: undefined, // Will be assigned in processRewardsAndPenalties() balances: undefined, }; diff --git a/packages/state-transition/src/cache/pubkeyCache.ts b/packages/state-transition/src/cache/pubkeyCache.ts index 44fe6df45592..f96436ec14f4 100644 --- a/packages/state-transition/src/cache/pubkeyCache.ts +++ b/packages/state-transition/src/cache/pubkeyCache.ts @@ -1,4 +1,5 @@ import {PublicKey} from "@chainsafe/blst"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import * as immutable from "immutable"; import {ValidatorIndex, phase0} from "@lodestar/types"; @@ -39,26 +40,6 @@ export function newUnfinalizedPubkeyIndexMap(): UnfinalizedPubkeyIndexMap { return immutable.Map(); } -export class PubkeyIndexMap { - // We don't really need the full pubkey. We could just use the first 20 bytes like an Ethereum address - readonly map = new Map(); - - get size(): number { - return this.map.size; - } - - /** - * Must support reading with string for API support where pubkeys are already strings - */ - get(key: Uint8Array | PubkeyHex): ValidatorIndex | undefined { - return this.map.get(toMemoryEfficientHexStr(key)); - } - - set(key: Uint8Array | PubkeyHex, value: ValidatorIndex): void { - this.map.set(toMemoryEfficientHexStr(key), value); - } -} - /** * Checks the pubkey indices against a state and adds missing pubkeys * diff --git a/packages/state-transition/src/cache/stateCache.ts b/packages/state-transition/src/cache/stateCache.ts index 0435e8829d21..3eeeb285b660 100644 --- a/packages/state-transition/src/cache/stateCache.ts +++ b/packages/state-transition/src/cache/stateCache.ts @@ -175,7 +175,7 @@ export function loadCachedBeaconState( // This cache is populated during epoch transition, and should be preserved for performance. // If the cache is missing too often, means that our clone strategy is not working well. export function isStateValidatorsNodesPopulated(state: CachedBeaconStateAllForks): boolean { + // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute return state.validators["nodesPopulated"] === true; } export function isStateBalancesNodesPopulated(state: CachedBeaconStateAllForks): boolean { + // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute return state.balances["nodesPopulated"] === true; } diff --git a/packages/state-transition/src/cache/syncCommitteeCache.ts b/packages/state-transition/src/cache/syncCommitteeCache.ts index b0a93f121369..b9a65302c3e2 100644 --- a/packages/state-transition/src/cache/syncCommitteeCache.ts +++ b/packages/state-transition/src/cache/syncCommitteeCache.ts @@ -1,7 +1,7 @@ import {CompositeViewDU} from "@chainsafe/ssz"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {ssz, ValidatorIndex} from "@lodestar/types"; import {toPubkeyHex} from "@lodestar/utils"; -import {PubkeyIndexMap} from "./pubkeyCache.js"; type SyncComitteeValidatorIndexMap = Map; @@ -82,7 +82,7 @@ function computeSyncCommitteeIndices( const pubkeys = syncCommittee.pubkeys.getAllReadonly(); for (const pubkey of pubkeys) { const validatorIndex = pubkey2index.get(pubkey); - if (validatorIndex === undefined) { + if (validatorIndex === null) { throw Error(`SyncCommittee pubkey is unknown ${toPubkeyHex(pubkey)}`); } diff --git a/packages/state-transition/src/constants/constants.ts b/packages/state-transition/src/constants/constants.ts index c3ff3f9ac79e..3afb4e687508 100644 --- a/packages/state-transition/src/constants/constants.ts +++ b/packages/state-transition/src/constants/constants.ts @@ -1,10 +1,12 @@ -export const ZERO_HASH = Buffer.alloc(32, 0); -export const EMPTY_SIGNATURE = Buffer.alloc(96, 0); +export const ZERO_HASH = new Uint8Array(32).fill(0); +export const EMPTY_SIGNATURE = new Uint8Array(96).fill(0); export const SECONDS_PER_DAY = 86400; export const BASE_REWARDS_PER_EPOCH = 4; -export const G2_POINT_AT_INFINITY = Buffer.from( - "c000000000000000000000000000000000000000000000000000000000000000" + - "0000000000000000000000000000000000000000000000000000000000000000" + - "0000000000000000000000000000000000000000000000000000000000000000", - "hex" +export const G2_POINT_AT_INFINITY = new Uint8Array( + Buffer.from( + "c000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000", + "hex" + ) ); diff --git a/packages/state-transition/src/epoch/index.ts b/packages/state-transition/src/epoch/index.ts index bfb415b9ed6a..b0b1651321d5 100644 --- a/packages/state-transition/src/epoch/index.ts +++ b/packages/state-transition/src/epoch/index.ts @@ -28,7 +28,7 @@ import {processRewardsAndPenalties} from "./processRewardsAndPenalties.js"; import {processSlashings} from "./processSlashings.js"; import {processSlashingsReset} from "./processSlashingsReset.js"; import {processSyncCommitteeUpdates} from "./processSyncCommitteeUpdates.js"; -import {processPendingBalanceDeposits} from "./processPendingBalanceDeposits.js"; +import {processPendingDeposits} from "./processPendingDeposits.js"; import {processPendingConsolidations} from "./processPendingConsolidations.js"; // For spec tests @@ -48,7 +48,7 @@ export { processParticipationFlagUpdates, processSyncCommitteeUpdates, processHistoricalSummariesUpdate, - processPendingBalanceDeposits, + processPendingDeposits, processPendingConsolidations, }; @@ -70,7 +70,7 @@ export enum EpochTransitionStep { processEffectiveBalanceUpdates = "processEffectiveBalanceUpdates", processParticipationFlagUpdates = "processParticipationFlagUpdates", processSyncCommitteeUpdates = "processSyncCommitteeUpdates", - processPendingBalanceDeposits = "processPendingBalanceDeposits", + processPendingDeposits = "processPendingDeposits", processPendingConsolidations = "processPendingConsolidations", } @@ -131,9 +131,9 @@ export function processEpoch( const stateElectra = state as CachedBeaconStateElectra; { const timer = metrics?.epochTransitionStepTime.startTimer({ - step: EpochTransitionStep.processPendingBalanceDeposits, + step: EpochTransitionStep.processPendingDeposits, }); - processPendingBalanceDeposits(stateElectra, cache); + processPendingDeposits(stateElectra, cache); timer?.(); } diff --git a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts index 0ea4b49dddf4..1fe5c92eea1a 100644 --- a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts +++ b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts @@ -5,12 +5,10 @@ import { HYSTERESIS_QUOTIENT, HYSTERESIS_UPWARD_MULTIPLIER, MAX_EFFECTIVE_BALANCE, - MAX_EFFECTIVE_BALANCE_ELECTRA, - MIN_ACTIVATION_BALANCE, TIMELY_TARGET_FLAG_INDEX, } from "@lodestar/params"; import {EpochTransitionCache, CachedBeaconStateAllForks, BeaconStateAltair} from "../types.js"; -import {hasCompoundingWithdrawalCredential} from "../util/electra.js"; +import {getMaxEffectiveBalance} from "../util/validator.js"; /** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */ const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; @@ -42,11 +40,10 @@ export function processEffectiveBalanceUpdates( // update effective balances with hysteresis // epochTransitionCache.balances is initialized in processRewardsAndPenalties() - // and updated in processPendingBalanceDeposits() and processPendingConsolidations() + // and updated in processPendingDeposits() and processPendingConsolidations() // so it's recycled here for performance. const balances = cache.balances ?? state.balances.getAll(); const currentEpochValidators = cache.validators; - const newCompoundingValidators = cache.newCompoundingValidators ?? new Set(); let numUpdate = 0; for (let i = 0, len = balances.length; i < len; i++) { @@ -61,10 +58,7 @@ export function processEffectiveBalanceUpdates( effectiveBalanceLimit = MAX_EFFECTIVE_BALANCE; } else { // from electra, effectiveBalanceLimit is per validator - const isCompoundingValidator = - hasCompoundingWithdrawalCredential(currentEpochValidators[i].withdrawalCredentials) || - newCompoundingValidators.has(i); - effectiveBalanceLimit = isCompoundingValidator ? MAX_EFFECTIVE_BALANCE_ELECTRA : MIN_ACTIVATION_BALANCE; + effectiveBalanceLimit = getMaxEffectiveBalance(currentEpochValidators[i].withdrawalCredentials); } if ( diff --git a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts b/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts deleted file mode 100644 index bef3ec0b2724..000000000000 --- a/packages/state-transition/src/epoch/processPendingBalanceDeposits.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {FAR_FUTURE_EPOCH} from "@lodestar/params"; -import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js"; -import {increaseBalance} from "../util/balance.js"; -import {getActivationExitChurnLimit} from "../util/validator.js"; - -/** - * Starting from Electra: - * Process pending balance deposits from state subject to churn limit and depsoitBalanceToConsume. - * For each eligible `deposit`, call `increaseBalance()`. - * Remove the processed deposits from `state.pendingBalanceDeposits`. - * Update `state.depositBalanceToConsume` for the next epoch - * - * TODO Electra: Update ssz library to support batch push to `pendingBalanceDeposits` - */ -export function processPendingBalanceDeposits(state: CachedBeaconStateElectra, cache: EpochTransitionCache): void { - const nextEpoch = state.epochCtx.epoch + 1; - const availableForProcessing = state.depositBalanceToConsume + BigInt(getActivationExitChurnLimit(state.epochCtx)); - let processedAmount = 0n; - let nextDepositIndex = 0; - const depositsToPostpone = []; - const validators = state.validators; - const cachedBalances = cache.balances; - - for (const deposit of state.pendingBalanceDeposits.getAllReadonly()) { - const {amount, index: depositIndex} = deposit; - const validator = validators.getReadonly(depositIndex); - - // Validator is exiting, postpone the deposit until after withdrawable epoch - if (validator.exitEpoch < FAR_FUTURE_EPOCH) { - if (nextEpoch <= validator.withdrawableEpoch) { - depositsToPostpone.push(deposit); - } else { - // Deposited balance will never become active. Increase balance but do not consume churn - increaseBalance(state, depositIndex, Number(amount)); - if (cachedBalances) { - cachedBalances[depositIndex] += Number(amount); - } - } - } else { - // Validator is not exiting, attempt to process deposit - if (processedAmount + amount > availableForProcessing) { - // Deposit does not fit in the churn, no more deposit processing in this epoch. - break; - } else { - // Deposit fits in the churn, process it. Increase balance and consume churn. - increaseBalance(state, depositIndex, Number(amount)); - if (cachedBalances) { - cachedBalances[depositIndex] += Number(amount); - } - processedAmount = processedAmount + amount; - } - } - // Regardless of how the deposit was handled, we move on in the queue. - nextDepositIndex++; - } - - const remainingPendingBalanceDeposits = state.pendingBalanceDeposits.sliceFrom(nextDepositIndex); - state.pendingBalanceDeposits = remainingPendingBalanceDeposits; - - if (remainingPendingBalanceDeposits.length === 0) { - state.depositBalanceToConsume = 0n; - } else { - state.depositBalanceToConsume = availableForProcessing - processedAmount; - } - - // TODO Electra: add a function in ListCompositeTreeView to support batch push operation - for (const deposit of depositsToPostpone) { - state.pendingBalanceDeposits.push(deposit); - } -} diff --git a/packages/state-transition/src/epoch/processPendingConsolidations.ts b/packages/state-transition/src/epoch/processPendingConsolidations.ts index 28178a509bba..0ec39409f8a7 100644 --- a/packages/state-transition/src/epoch/processPendingConsolidations.ts +++ b/packages/state-transition/src/epoch/processPendingConsolidations.ts @@ -1,8 +1,6 @@ -import {ValidatorIndex} from "@lodestar/types"; import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js"; import {decreaseBalance, increaseBalance} from "../util/balance.js"; -import {getActiveBalance} from "../util/validator.js"; -import {switchToCompoundingValidator} from "../util/electra.js"; +import {getMaxEffectiveBalance} from "../util/validator.js"; /** * Starting from Electra: @@ -22,7 +20,6 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra, ca let nextPendingConsolidation = 0; const validators = state.validators; const cachedBalances = cache.balances; - const newCompoundingValidators = new Set(); for (const pendingConsolidation of state.pendingConsolidations.getAllReadonly()) { const {sourceIndex, targetIndex} = pendingConsolidation; @@ -36,21 +33,18 @@ export function processPendingConsolidations(state: CachedBeaconStateElectra, ca if (sourceValidator.withdrawableEpoch > nextEpoch) { break; } - // Churn any target excess active balance of target and raise its max - switchToCompoundingValidator(state, targetIndex); - newCompoundingValidators.add(targetIndex); // Move active balance to target. Excess balance is withdrawable. - const activeBalance = getActiveBalance(state, sourceIndex); - decreaseBalance(state, sourceIndex, activeBalance); - increaseBalance(state, targetIndex, activeBalance); + const maxEffectiveBalance = getMaxEffectiveBalance(state.validators.getReadonly(sourceIndex).withdrawalCredentials); + const sourceEffectiveBalance = Math.min(state.balances.get(sourceIndex), maxEffectiveBalance); + decreaseBalance(state, sourceIndex, sourceEffectiveBalance); + increaseBalance(state, targetIndex, sourceEffectiveBalance); if (cachedBalances) { - cachedBalances[sourceIndex] -= activeBalance; - cachedBalances[targetIndex] += activeBalance; + cachedBalances[sourceIndex] -= sourceEffectiveBalance; + cachedBalances[targetIndex] += sourceEffectiveBalance; } nextPendingConsolidation++; } - cache.newCompoundingValidators = newCompoundingValidators; state.pendingConsolidations = state.pendingConsolidations.sliceFrom(nextPendingConsolidation); } diff --git a/packages/state-transition/src/epoch/processPendingDeposits.ts b/packages/state-transition/src/epoch/processPendingDeposits.ts new file mode 100644 index 000000000000..53af3ab38763 --- /dev/null +++ b/packages/state-transition/src/epoch/processPendingDeposits.ts @@ -0,0 +1,117 @@ +import {FAR_FUTURE_EPOCH, ForkSeq, GENESIS_SLOT, MAX_PENDING_DEPOSITS_PER_EPOCH} from "@lodestar/params"; +import {PendingDeposit} from "@lodestar/types/lib/electra/types.js"; +import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js"; +import {increaseBalance} from "../util/balance.js"; +import {getActivationExitChurnLimit} from "../util/validator.js"; +import {computeStartSlotAtEpoch} from "../util/epoch.js"; +import {addValidatorToRegistry, isValidDepositSignature} from "../block/processDeposit.js"; + +/** + * Starting from Electra: + * Process pending balance deposits from state subject to churn limit and depsoitBalanceToConsume. + * For each eligible `deposit`, call `increaseBalance()`. + * Remove the processed deposits from `state.pendingDeposits`. + * Update `state.depositBalanceToConsume` for the next epoch + * + * TODO Electra: Update ssz library to support batch push to `pendingDeposits` + */ +export function processPendingDeposits(state: CachedBeaconStateElectra, cache: EpochTransitionCache): void { + const nextEpoch = state.epochCtx.epoch + 1; + const availableForProcessing = state.depositBalanceToConsume + BigInt(getActivationExitChurnLimit(state.epochCtx)); + let processedAmount = 0; + let nextDepositIndex = 0; + const depositsToPostpone = []; + let isChurnLimitReached = false; + const finalizedSlot = computeStartSlotAtEpoch(state.finalizedCheckpoint.epoch); + + for (const deposit of state.pendingDeposits.getAllReadonly()) { + // Do not process deposit requests if Eth1 bridge deposits are not yet applied. + if ( + // Is deposit request + deposit.slot > GENESIS_SLOT && + // There are pending Eth1 bridge deposits + state.eth1DepositIndex < state.depositRequestsStartIndex + ) { + break; + } + + // Check if deposit has been finalized, otherwise, stop processing. + if (deposit.slot > finalizedSlot) { + break; + } + + // Check if number of processed deposits has not reached the limit, otherwise, stop processing. + if (nextDepositIndex >= MAX_PENDING_DEPOSITS_PER_EPOCH) { + break; + } + + // Read validator state + let isValidatorExited = false; + let isValidatorWithdrawn = false; + + const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey); + if (validatorIndex !== null) { + const validator = state.validators.getReadonly(validatorIndex); + isValidatorExited = validator.exitEpoch < FAR_FUTURE_EPOCH; + isValidatorWithdrawn = validator.withdrawableEpoch < nextEpoch; + } + + if (isValidatorWithdrawn) { + // Deposited balance will never become active. Increase balance but do not consume churn + applyPendingDeposit(state, deposit, cache); + } else if (isValidatorExited) { + // Validator is exiting, postpone the deposit until after withdrawable epoch + depositsToPostpone.push(deposit); + } else { + // Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch. + isChurnLimitReached = processedAmount + deposit.amount > availableForProcessing; + if (isChurnLimitReached) { + break; + } + // Consume churn and apply deposit. + processedAmount += deposit.amount; + applyPendingDeposit(state, deposit, cache); + } + + // Regardless of how the deposit was handled, we move on in the queue. + nextDepositIndex++; + } + + const remainingPendingDeposits = state.pendingDeposits.sliceFrom(nextDepositIndex); + state.pendingDeposits = remainingPendingDeposits; + + // TODO Electra: add a function in ListCompositeTreeView to support batch push operation + for (const deposit of depositsToPostpone) { + state.pendingDeposits.push(deposit); + } + + // Accumulate churn only if the churn limit has been hit. + if (isChurnLimitReached) { + state.depositBalanceToConsume = availableForProcessing - BigInt(processedAmount); + } else { + state.depositBalanceToConsume = 0n; + } +} + +function applyPendingDeposit( + state: CachedBeaconStateElectra, + deposit: PendingDeposit, + cache: EpochTransitionCache +): void { + const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey); + const {pubkey, withdrawalCredentials, amount, signature} = deposit; + const cachedBalances = cache.balances; + + if (validatorIndex === null) { + // Verify the deposit signature (proof of possession) which is not checked by the deposit contract + if (isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)) { + addValidatorToRegistry(ForkSeq.electra, state, pubkey, withdrawalCredentials, amount); + } + } else { + // Increase balance + increaseBalance(state, validatorIndex, amount); + if (cachedBalances) { + cachedBalances[validatorIndex] += amount; + } + } +} diff --git a/packages/state-transition/src/epoch/processSlashings.ts b/packages/state-transition/src/epoch/processSlashings.ts index ba4b483dffc2..23ee815cabb3 100644 --- a/packages/state-transition/src/epoch/processSlashings.ts +++ b/packages/state-transition/src/epoch/processSlashings.ts @@ -50,6 +50,10 @@ export function processSlashings( totalBalanceByIncrement ); const increment = EFFECTIVE_BALANCE_INCREMENT; + + const penaltyPerEffectiveBalanceIncrement = Math.floor( + (adjustedTotalSlashingBalanceByIncrement * increment) / totalBalanceByIncrement + ); const penalties: number[] = []; const penaltiesByEffectiveBalanceIncrement = new Map(); @@ -57,8 +61,12 @@ export function processSlashings( const effectiveBalanceIncrement = effectiveBalanceIncrements[index]; let penalty = penaltiesByEffectiveBalanceIncrement.get(effectiveBalanceIncrement); if (penalty === undefined) { - const penaltyNumeratorByIncrement = effectiveBalanceIncrement * adjustedTotalSlashingBalanceByIncrement; - penalty = Math.floor(penaltyNumeratorByIncrement / totalBalanceByIncrement) * increment; + if (fork < ForkSeq.electra) { + const penaltyNumeratorByIncrement = effectiveBalanceIncrement * adjustedTotalSlashingBalanceByIncrement; + penalty = Math.floor(penaltyNumeratorByIncrement / totalBalanceByIncrement) * increment; + } else { + penalty = penaltyPerEffectiveBalanceIncrement * effectiveBalanceIncrement; + } penaltiesByEffectiveBalanceIncrement.set(effectiveBalanceIncrement, penalty); } diff --git a/packages/state-transition/src/epoch/processSyncCommitteeUpdates.ts b/packages/state-transition/src/epoch/processSyncCommitteeUpdates.ts index df4e0364be17..f01f2055420a 100644 --- a/packages/state-transition/src/epoch/processSyncCommitteeUpdates.ts +++ b/packages/state-transition/src/epoch/processSyncCommitteeUpdates.ts @@ -14,7 +14,7 @@ export function processSyncCommitteeUpdates(fork: ForkSeq, state: CachedBeaconSt const nextEpoch = state.epochCtx.epoch + 1; if (nextEpoch % EPOCHS_PER_SYNC_COMMITTEE_PERIOD === 0) { - const activeValidatorIndices = state.epochCtx.nextShuffling.activeIndices; + const activeValidatorIndices = state.epochCtx.nextActiveIndices; const {effectiveBalanceIncrements} = state.epochCtx; const nextSyncCommitteeIndices = getNextSyncCommitteeIndices( diff --git a/packages/state-transition/src/index.ts b/packages/state-transition/src/index.ts index 4ed801e3c490..600bbf173462 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -41,11 +41,11 @@ export { EpochCacheError, EpochCacheErrorCode, } from "./cache/epochCache.js"; +export {toMemoryEfficientHexStr} from "./cache/pubkeyCache.js"; export {type EpochTransitionCache, beforeProcessEpoch} from "./cache/epochTransitionCache.js"; // Aux data-structures export { - PubkeyIndexMap, type Index2PubkeyCache, type UnfinalizedPubkeyIndexMap, newUnfinalizedPubkeyIndexMap, diff --git a/packages/state-transition/src/metrics.ts b/packages/state-transition/src/metrics.ts index a5e5463231fa..ac558a1be139 100644 --- a/packages/state-transition/src/metrics.ts +++ b/packages/state-transition/src/metrics.ts @@ -74,9 +74,11 @@ export function onPostStateMetrics(postState: CachedBeaconStateAllForks, metrics // This cache is populated during epoch transition, and should be preserved for performance. // If the cache is missing too often, means that our clone strategy is not working well. function isValidatorsNodesPopulated(state: CachedBeaconStateAllForks): boolean { + // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute return state.validators["nodesPopulated"] === true; } function isBalancesNodesPopulated(state: CachedBeaconStateAllForks): boolean { + // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute return state.balances["nodesPopulated"] === true; } diff --git a/packages/state-transition/src/signatureSets/attesterSlashings.ts b/packages/state-transition/src/signatureSets/attesterSlashings.ts index 8088c2522282..36f31d97e083 100644 --- a/packages/state-transition/src/signatureSets/attesterSlashings.ts +++ b/packages/state-transition/src/signatureSets/attesterSlashings.ts @@ -8,9 +8,9 @@ export function getAttesterSlashingsSignatureSets( state: CachedBeaconStateAllForks, signedBlock: SignedBeaconBlock ): ISignatureSet[] { - return signedBlock.message.body.attesterSlashings - .map((attesterSlashing) => getAttesterSlashingSignatureSets(state, attesterSlashing)) - .flat(1); + return signedBlock.message.body.attesterSlashings.flatMap((attesterSlashing) => + getAttesterSlashingSignatureSets(state, attesterSlashing) + ); } /** Get signature sets from a single AttesterSlashing object */ diff --git a/packages/state-transition/src/signatureSets/proposerSlashings.ts b/packages/state-transition/src/signatureSets/proposerSlashings.ts index 8a004225111d..b0c1aa465bd5 100644 --- a/packages/state-transition/src/signatureSets/proposerSlashings.ts +++ b/packages/state-transition/src/signatureSets/proposerSlashings.ts @@ -35,7 +35,7 @@ export function getProposerSlashingsSignatureSets( state: CachedBeaconStateAllForks, signedBlock: SignedBeaconBlock ): ISignatureSet[] { - return signedBlock.message.body.proposerSlashings - .map((proposerSlashing) => getProposerSlashingSignatureSets(state, proposerSlashing)) - .flat(1); + return signedBlock.message.body.proposerSlashings.flatMap((proposerSlashing) => + getProposerSlashingSignatureSets(state, proposerSlashing) + ); } diff --git a/packages/state-transition/src/slot/upgradeStateToAltair.ts b/packages/state-transition/src/slot/upgradeStateToAltair.ts index fa7e6fbeba8e..22ab8b13882c 100644 --- a/packages/state-transition/src/slot/upgradeStateToAltair.ts +++ b/packages/state-transition/src/slot/upgradeStateToAltair.ts @@ -72,7 +72,7 @@ export function upgradeStateToAltair(statePhase0: CachedBeaconStatePhase0): Cach const {syncCommittee, indices} = getNextSyncCommittee( ForkSeq.altair, stateAltair, - stateAltair.epochCtx.nextShuffling.activeIndices, + stateAltair.epochCtx.nextActiveIndices, stateAltair.epochCtx.effectiveBalanceIncrements ); const syncCommitteeView = ssz.altair.SyncCommittee.toViewDU(syncCommittee); @@ -92,6 +92,7 @@ export function upgradeStateToAltair(statePhase0: CachedBeaconStatePhase0): Cach // // TODO: This could only drop the caches of index 15,16. However this would couple this code tightly with SSZ ViewDU // internals. If the cache is not cleared, consuming the ViewDU instance could break in strange ways. + // biome-ignore lint/complexity/useLiteralKeys: It is a protected attribute stateAltair["clearCache"](); // TODO: describe issue. Compute progressive target balances diff --git a/packages/state-transition/src/slot/upgradeStateToCapella.ts b/packages/state-transition/src/slot/upgradeStateToCapella.ts index ce27a17bf9b0..30a0701e58e8 100644 --- a/packages/state-transition/src/slot/upgradeStateToCapella.ts +++ b/packages/state-transition/src/slot/upgradeStateToCapella.ts @@ -64,6 +64,7 @@ export function upgradeStateToCapella(stateBellatrix: CachedBeaconStateBellatrix // Commit new added fields ViewDU to the root node stateCapella.commit(); // Clear cache to ensure the cache of bellatrix fields is not used by new capella fields + // biome-ignore lint/complexity/useLiteralKeys: It is a protected attribute stateCapella["clearCache"](); return stateCapella; diff --git a/packages/state-transition/src/slot/upgradeStateToDeneb.ts b/packages/state-transition/src/slot/upgradeStateToDeneb.ts index 53be75f72ad8..2344a8d4e08e 100644 --- a/packages/state-transition/src/slot/upgradeStateToDeneb.ts +++ b/packages/state-transition/src/slot/upgradeStateToDeneb.ts @@ -34,6 +34,7 @@ export function upgradeStateToDeneb(stateCapella: CachedBeaconStateCapella): Cac stateDeneb.commit(); // Clear cache to ensure the cache of capella fields is not used by new deneb fields + // biome-ignore lint/complexity/useLiteralKeys: It is a protected attribute stateDeneb["clearCache"](); return stateDeneb; diff --git a/packages/state-transition/src/slot/upgradeStateToElectra.ts b/packages/state-transition/src/slot/upgradeStateToElectra.ts index 6600ad98e80a..a3c8981ab13f 100644 --- a/packages/state-transition/src/slot/upgradeStateToElectra.ts +++ b/packages/state-transition/src/slot/upgradeStateToElectra.ts @@ -1,14 +1,11 @@ import {Epoch, ValidatorIndex, ssz} from "@lodestar/types"; -import {FAR_FUTURE_EPOCH, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params"; +import {FAR_FUTURE_EPOCH, GENESIS_SLOT, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params"; import {CachedBeaconStateDeneb} from "../types.js"; import {CachedBeaconStateElectra, getCachedBeaconState} from "../cache/stateCache.js"; -import { - hasCompoundingWithdrawalCredential, - queueEntireBalanceAndResetValidator, - queueExcessActiveBalance, -} from "../util/electra.js"; +import {hasCompoundingWithdrawalCredential, queueExcessActiveBalance} from "../util/electra.js"; import {computeActivationExitEpoch} from "../util/epoch.js"; import {getActivationExitChurnLimit, getConsolidationChurnLimit} from "../util/validator.js"; +import {G2_POINT_AT_INFINITY} from "../constants/constants.js"; /** * Upgrade a state from Deneb to Electra. @@ -48,17 +45,11 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache stateElectraView.inactivityScores = stateElectraCloned.inactivityScores; stateElectraView.currentSyncCommittee = stateElectraCloned.currentSyncCommittee; stateElectraView.nextSyncCommittee = stateElectraCloned.nextSyncCommittee; - stateElectraView.latestExecutionPayloadHeader = ssz.electra.BeaconState.fields.latestExecutionPayloadHeader.toViewDU({ - ...stateElectraCloned.latestExecutionPayloadHeader.toValue(), - depositRequestsRoot: ssz.Root.defaultValue(), - withdrawalRequestsRoot: ssz.Root.defaultValue(), - consolidationRequestsRoot: ssz.Root.defaultValue(), - }); + stateElectraView.latestExecutionPayloadHeader = stateElectraCloned.latestExecutionPayloadHeader; stateElectraView.nextWithdrawalIndex = stateDeneb.nextWithdrawalIndex; stateElectraView.nextWithdrawalValidatorIndex = stateDeneb.nextWithdrawalValidatorIndex; stateElectraView.historicalSummaries = stateElectraCloned.historicalSummaries; - // latestExecutionPayloadHeader's depositRequestsRoot and withdrawalRequestsRoot set to zeros by default // default value of depositRequestsStartIndex is UNSET_DEPOSIT_REQUESTS_START_INDEX stateElectraView.depositRequestsStartIndex = UNSET_DEPOSIT_REQUESTS_START_INDEX; stateElectraView.depositBalanceToConsume = BigInt(0); @@ -87,8 +78,6 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache stateElectraView.earliestExitEpoch = Math.max(...exitEpochs) + 1; stateElectraView.consolidationBalanceToConsume = BigInt(0); stateElectraView.earliestConsolidationEpoch = computeActivationExitEpoch(currentEpochPre); - // stateElectraView.pendingBalanceDeposits = ssz.electra.PendingBalanceDeposits.defaultViewDU(); - // pendingBalanceDeposits, pendingPartialWithdrawals, pendingConsolidations are default values // TODO-electra: can we improve this? stateElectraView.commit(); const tmpElectraState = getCachedBeaconState(stateElectraView, stateDeneb); @@ -101,7 +90,23 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache }); for (const validatorIndex of preActivation) { - queueEntireBalanceAndResetValidator(stateElectraView as CachedBeaconStateElectra, validatorIndex); + const stateElectra = stateElectraView as CachedBeaconStateElectra; + const balance = stateElectra.balances.get(validatorIndex); + stateElectra.balances.set(validatorIndex, 0); + + const validator = stateElectra.validators.get(validatorIndex); + validator.effectiveBalance = 0; + stateElectra.epochCtx.effectiveBalanceIncrementsSet(validatorIndex, 0); + validator.activationEligibilityEpoch = FAR_FUTURE_EPOCH; + + const pendingDeposit = ssz.electra.PendingDeposit.toViewDU({ + pubkey: validator.pubkey, + withdrawalCredentials: validator.withdrawalCredentials, + amount: balance, + signature: G2_POINT_AT_INFINITY, + slot: GENESIS_SLOT, + }); + stateElectra.pendingDeposits.push(pendingDeposit); } for (let i = 0; i < validatorsArr.length; i++) { @@ -118,49 +123,7 @@ export function upgradeStateToElectra(stateDeneb: CachedBeaconStateDeneb): Cache // Commit new added fields ViewDU to the root node stateElectra.commit(); // Clear cache to ensure the cache of deneb fields is not used by new ELECTRA fields - stateElectra["clearCache"](); - - return stateElectra; -} - -export function upgradeStateToElectraOriginal(stateDeneb: CachedBeaconStateDeneb): CachedBeaconStateElectra { - const {config} = stateDeneb; - - const stateElectraNode = ssz.deneb.BeaconState.commitViewDU(stateDeneb); - const stateElectraView = ssz.electra.BeaconState.getViewDU(stateElectraNode); - - const stateElectra = getCachedBeaconState(stateElectraView, stateDeneb); - - stateElectra.fork = ssz.phase0.Fork.toViewDU({ - previousVersion: stateDeneb.fork.currentVersion, - currentVersion: config.ELECTRA_FORK_VERSION, - epoch: stateDeneb.epochCtx.epoch, - }); - - // latestExecutionPayloadHeader's depositRequestsRoot and withdrawalRequestsRoot set to zeros by default - // default value of depositRequestsStartIndex is UNSET_DEPOSIT_REQUESTS_START_INDEX - stateElectra.depositRequestsStartIndex = UNSET_DEPOSIT_REQUESTS_START_INDEX; - - const validatorsArr = stateElectra.validators.getAllReadonly(); - - for (let i = 0; i < validatorsArr.length; i++) { - const validator = validatorsArr[i]; - - // [EIP-7251]: add validators that are not yet active to pending balance deposits - if (validator.activationEligibilityEpoch === FAR_FUTURE_EPOCH) { - queueEntireBalanceAndResetValidator(stateElectra, i); - } - - // [EIP-7251]: Ensure early adopters of compounding credentials go through the activation churn - const withdrawalCredential = validator.withdrawalCredentials; - if (hasCompoundingWithdrawalCredential(withdrawalCredential)) { - queueExcessActiveBalance(stateElectra, i); - } - } - - // Commit new added fields ViewDU to the root node - stateElectra.commit(); - // Clear cache to ensure the cache of deneb fields is not used by new ELECTRA fields + // biome-ignore lint/complexity/useLiteralKeys: It is a protected attribute stateElectra["clearCache"](); return stateElectra; diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index 3b97f19282a4..f025c685b1a6 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -167,6 +167,25 @@ export function processSlots( /** * All processSlot() logic but separate so stateTransition() can recycle the caches + * + * Epoch transition will be processed at the last slot of an epoch. Note that compute_shuffling() is going + * to be executed in parallel (either by napi-rs or worker thread) with processEpoch() like below: + * + * state-transition + * ╔══════════════════════════════════════════════════════════════════════════════════╗ + * ║ beforeProcessEpoch processEpoch afterPRocessEpoch ║ + * ║ |-------------------------|--------------------|-------------------------------|║ + * ║ | | | ║ + * ╚═══════════════════════|═══════════════════════════════|══════════════════════════╝ + * | | + * build() get() + * | | + * ╔═══════════════════════V═══════════════════════════════V═══════════════════════════╗ + * ║ | | ║ + * ║ |-------------------------------| ║ + * ║ compute_shuffling() ║ + * ╚═══════════════════════════════════════════════════════════════════════════════════╝ + * beacon-node ShufflingCache */ function processSlotsWithTransientCache( postState: CachedBeaconStateAllForks, diff --git a/packages/state-transition/src/util/balance.ts b/packages/state-transition/src/util/balance.ts index e9b7a06e4130..c6b196846ec9 100644 --- a/packages/state-transition/src/util/balance.ts +++ b/packages/state-transition/src/util/balance.ts @@ -2,7 +2,7 @@ import {EFFECTIVE_BALANCE_INCREMENT} from "@lodestar/params"; import {Gwei, ValidatorIndex} from "@lodestar/types"; import {bigIntMax} from "@lodestar/utils"; import {EffectiveBalanceIncrements} from "../cache/effectiveBalanceIncrements.js"; -import {BeaconStateAllForks} from ".."; +import {BeaconStateAllForks} from "../index.js"; import {CachedBeaconStateAllForks} from "../types.js"; /** diff --git a/packages/state-transition/src/util/blindedBlock.ts b/packages/state-transition/src/util/blindedBlock.ts index 2e4e4d590817..1793ff37255e 100644 --- a/packages/state-transition/src/util/blindedBlock.ts +++ b/packages/state-transition/src/util/blindedBlock.ts @@ -25,9 +25,13 @@ export function blindedOrFullBlockHashTreeRoot( ): Root { return isBlindedBeaconBlock(blindedOrFull) ? // Blinded - config.getExecutionForkTypes(blindedOrFull.slot).BlindedBeaconBlock.hashTreeRoot(blindedOrFull) + config + .getExecutionForkTypes(blindedOrFull.slot) + .BlindedBeaconBlock.hashTreeRoot(blindedOrFull) : // Full - config.getForkTypes(blindedOrFull.slot).BeaconBlock.hashTreeRoot(blindedOrFull); + config + .getForkTypes(blindedOrFull.slot) + .BeaconBlock.hashTreeRoot(blindedOrFull); } export function blindedOrFullBlockToHeader( @@ -36,9 +40,13 @@ export function blindedOrFullBlockToHeader( ): BeaconBlockHeader { const bodyRoot = isBlindedBeaconBlock(blindedOrFull) ? // Blinded - config.getExecutionForkTypes(blindedOrFull.slot).BlindedBeaconBlockBody.hashTreeRoot(blindedOrFull.body) + config + .getExecutionForkTypes(blindedOrFull.slot) + .BlindedBeaconBlockBody.hashTreeRoot(blindedOrFull.body) : // Full - config.getForkTypes(blindedOrFull.slot).BeaconBlockBody.hashTreeRoot(blindedOrFull.body); + config + .getForkTypes(blindedOrFull.slot) + .BeaconBlockBody.hashTreeRoot(blindedOrFull.body); return { slot: blindedOrFull.slot, @@ -94,12 +102,11 @@ export function parseExecutionPayloadAndBlobsBundle(data: ExecutionPayload | Exe } { if (isExecutionPayloadAndBlobsBundle(data)) { return data; - } else { - return { - executionPayload: data, - blobsBundle: null, - }; } + return { + executionPayload: data, + blobsBundle: null, + }; } export function reconstructFullBlockOrContents( @@ -120,7 +127,6 @@ export function reconstructFullBlockOrContents( } return {signedBlock, ...contents} as SignedBeaconBlockOrContents; - } else { - return signedBlock as SignedBeaconBlockOrContents; } + return signedBlock as SignedBeaconBlockOrContents; } diff --git a/packages/state-transition/src/util/calculateCommitteeAssignments.ts b/packages/state-transition/src/util/calculateCommitteeAssignments.ts new file mode 100644 index 000000000000..992c5efbdaaa --- /dev/null +++ b/packages/state-transition/src/util/calculateCommitteeAssignments.ts @@ -0,0 +1,43 @@ +import {CommitteeIndex, Slot, ValidatorIndex} from "@lodestar/types"; +import {SLOTS_PER_EPOCH} from "@lodestar/params"; +import {EpochShuffling} from "./epochShuffling.js"; + +// Copied from lodestar-api package to avoid depending on the package +export interface AttesterDuty { + validatorIndex: ValidatorIndex; + committeeIndex: CommitteeIndex; + committeeLength: number; + committeesAtSlot: number; + validatorCommitteeIndex: number; + slot: Slot; +} + +export function calculateCommitteeAssignments( + epochShuffling: EpochShuffling, + requestedValidatorIndices: ValidatorIndex[] +): Map { + const requestedValidatorIndicesSet = new Set(requestedValidatorIndices); + const duties = new Map(); + + const epochCommittees = epochShuffling.committees; + for (let epochSlot = 0; epochSlot < SLOTS_PER_EPOCH; epochSlot++) { + const slotCommittees = epochCommittees[epochSlot]; + for (let i = 0, committeesAtSlot = slotCommittees.length; i < committeesAtSlot; i++) { + for (let j = 0, committeeLength = slotCommittees[i].length; j < committeeLength; j++) { + const validatorIndex = slotCommittees[i][j]; + if (requestedValidatorIndicesSet.has(validatorIndex)) { + duties.set(validatorIndex, { + validatorIndex, + committeeLength, + committeesAtSlot, + validatorCommitteeIndex: j, + committeeIndex: i, + slot: epochShuffling.epoch * SLOTS_PER_EPOCH + epochSlot, + }); + } + } + } + } + + return duties; +} diff --git a/packages/state-transition/src/util/computeAnchorCheckpoint.ts b/packages/state-transition/src/util/computeAnchorCheckpoint.ts new file mode 100644 index 000000000000..e37ffc2c632d --- /dev/null +++ b/packages/state-transition/src/util/computeAnchorCheckpoint.ts @@ -0,0 +1,38 @@ +import {ChainForkConfig} from "@lodestar/config"; +import {ssz, phase0} from "@lodestar/types"; +import {GENESIS_SLOT, ZERO_HASH} from "@lodestar/params"; +import {BeaconStateAllForks} from "../types.js"; +import {blockToHeader} from "./blockRoot.js"; +import {computeCheckpointEpochAtStateSlot} from "./epoch.js"; + +export function computeAnchorCheckpoint( + config: ChainForkConfig, + anchorState: BeaconStateAllForks +): {checkpoint: phase0.Checkpoint; blockHeader: phase0.BeaconBlockHeader} { + let blockHeader: phase0.BeaconBlockHeader; + let root: Uint8Array; + const blockTypes = config.getForkTypes(anchorState.latestBlockHeader.slot); + + if (anchorState.latestBlockHeader.slot === GENESIS_SLOT) { + const block = blockTypes.BeaconBlock.defaultValue(); + block.stateRoot = anchorState.hashTreeRoot(); + blockHeader = blockToHeader(config, block); + root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader); + } else { + blockHeader = ssz.phase0.BeaconBlockHeader.clone(anchorState.latestBlockHeader); + if (ssz.Root.equals(blockHeader.stateRoot, ZERO_HASH)) { + blockHeader.stateRoot = anchorState.hashTreeRoot(); + } + root = ssz.phase0.BeaconBlockHeader.hashTreeRoot(blockHeader); + } + + return { + checkpoint: { + root, + // the checkpoint epoch = computeEpochAtSlot(anchorState.slot) + 1 if slot is not at epoch boundary + // this is similar to a process_slots() call + epoch: computeCheckpointEpochAtStateSlot(anchorState.slot), + }, + blockHeader, + }; +} diff --git a/packages/state-transition/src/util/deposit.ts b/packages/state-transition/src/util/deposit.ts index e8ef93c515d2..4370a13c783c 100644 --- a/packages/state-transition/src/util/deposit.ts +++ b/packages/state-transition/src/util/deposit.ts @@ -15,10 +15,8 @@ export function getEth1DepositCount(state: CachedBeaconStateAllForks, eth1Data?: if (state.eth1DepositIndex < eth1DataIndexLimit) { return Math.min(MAX_DEPOSITS, eth1DataIndexLimit - state.eth1DepositIndex); - } else { - return 0; } - } else { - return Math.min(MAX_DEPOSITS, eth1DataToUse.depositCount - state.eth1DepositIndex); + return 0; } + return Math.min(MAX_DEPOSITS, eth1DataToUse.depositCount - state.eth1DepositIndex); } diff --git a/packages/state-transition/src/util/electra.ts b/packages/state-transition/src/util/electra.ts index ac34da6407de..f1082c6d4603 100644 --- a/packages/state-transition/src/util/electra.ts +++ b/packages/state-transition/src/util/electra.ts @@ -1,6 +1,7 @@ -import {COMPOUNDING_WITHDRAWAL_PREFIX, FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; +import {COMPOUNDING_WITHDRAWAL_PREFIX, GENESIS_SLOT, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; import {ValidatorIndex, ssz} from "@lodestar/types"; import {CachedBeaconStateElectra} from "../types.js"; +import {G2_POINT_AT_INFINITY} from "../constants/constants.js"; import {hasEth1WithdrawalCredential} from "./capella.js"; export function hasCompoundingWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { @@ -16,43 +17,31 @@ export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Arr export function switchToCompoundingValidator(state: CachedBeaconStateElectra, index: ValidatorIndex): void { const validator = state.validators.get(index); - if (hasEth1WithdrawalCredential(validator.withdrawalCredentials)) { - // directly modifying the byte leads to ssz missing the modification resulting into - // wrong root compute, although slicing can be avoided but anyway this is not going - // to be a hot path so its better to clean slice and avoid side effects - const newWithdrawalCredentials = validator.withdrawalCredentials.slice(); - newWithdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX; - validator.withdrawalCredentials = newWithdrawalCredentials; - queueExcessActiveBalance(state, index); - } + // directly modifying the byte leads to ssz missing the modification resulting into + // wrong root compute, although slicing can be avoided but anyway this is not going + // to be a hot path so its better to clean slice and avoid side effects + const newWithdrawalCredentials = validator.withdrawalCredentials.slice(); + newWithdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX; + validator.withdrawalCredentials = newWithdrawalCredentials; + queueExcessActiveBalance(state, index); } export function queueExcessActiveBalance(state: CachedBeaconStateElectra, index: ValidatorIndex): void { const balance = state.balances.get(index); if (balance > MIN_ACTIVATION_BALANCE) { + const validator = state.validators.getReadonly(index); const excessBalance = balance - MIN_ACTIVATION_BALANCE; state.balances.set(index, MIN_ACTIVATION_BALANCE); - const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ - index, - amount: BigInt(excessBalance), + const pendingDeposit = ssz.electra.PendingDeposit.toViewDU({ + pubkey: validator.pubkey, + withdrawalCredentials: validator.withdrawalCredentials, + amount: excessBalance, + // Use bls.G2_POINT_AT_INFINITY as a signature field placeholder + signature: G2_POINT_AT_INFINITY, + // Use GENESIS_SLOT to distinguish from a pending deposit request + slot: GENESIS_SLOT, }); - state.pendingBalanceDeposits.push(pendingBalanceDeposit); + state.pendingDeposits.push(pendingDeposit); } } - -export function queueEntireBalanceAndResetValidator(state: CachedBeaconStateElectra, index: ValidatorIndex): void { - const balance = state.balances.get(index); - state.balances.set(index, 0); - - const validator = state.validators.get(index); - validator.effectiveBalance = 0; - state.epochCtx.effectiveBalanceIncrementsSet(index, 0); - validator.activationEligibilityEpoch = FAR_FUTURE_EPOCH; - - const pendingBalanceDeposit = ssz.electra.PendingBalanceDeposit.toViewDU({ - index, - amount: BigInt(balance), - }); - state.pendingBalanceDeposits.push(pendingBalanceDeposit); -} diff --git a/packages/state-transition/src/util/epochShuffling.ts b/packages/state-transition/src/util/epochShuffling.ts index 856721d8d083..6f63a2f4f5f8 100644 --- a/packages/state-transition/src/util/epochShuffling.ts +++ b/packages/state-transition/src/util/epochShuffling.ts @@ -1,16 +1,61 @@ -import {Epoch, RootHex, ValidatorIndex} from "@lodestar/types"; -import {intDiv, toRootHex} from "@lodestar/utils"; +import {asyncUnshuffleList, unshuffleList} from "@chainsafe/swap-or-not-shuffle"; +import {Epoch, RootHex, ssz, ValidatorIndex} from "@lodestar/types"; +import {GaugeExtra, intDiv, Logger, NoLabels, toRootHex} from "@lodestar/utils"; import { DOMAIN_BEACON_ATTESTER, + GENESIS_SLOT, MAX_COMMITTEES_PER_SLOT, SLOTS_PER_EPOCH, TARGET_COMMITTEE_SIZE, + SHUFFLE_ROUND_COUNT, } from "@lodestar/params"; +import {BeaconConfig} from "@lodestar/config"; import {BeaconStateAllForks} from "../types.js"; import {getSeed} from "./seed.js"; -import {unshuffleList} from "./shuffle.js"; import {computeStartSlotAtEpoch} from "./epoch.js"; import {getBlockRootAtSlot} from "./blockRoot.js"; +import {computeAnchorCheckpoint} from "./computeAnchorCheckpoint.js"; + +export interface ShufflingBuildProps { + state: BeaconStateAllForks; + activeIndices: Uint32Array; +} + +export interface PublicShufflingCacheMetrics { + shufflingCache: { + nextShufflingNotOnEpochCache: GaugeExtra; + }; +} +export interface IShufflingCache { + metrics: PublicShufflingCacheMetrics | null; + logger: Logger | null; + /** + * Gets a cached shuffling via the epoch and decision root. If the state and + * activeIndices are passed and a shuffling is not available it will be built + * synchronously. If the state is not passed and the shuffling is not available + * nothing will be returned. + * + * NOTE: If a shuffling is already queued and not calculated it will build and resolve + * the promise but the already queued build will happen at some later time + */ + getSync( + epoch: Epoch, + decisionRoot: RootHex, + buildProps?: T + ): T extends ShufflingBuildProps ? EpochShuffling : EpochShuffling | null; + + /** + * Gets a cached shuffling via the epoch and decision root. Returns a promise + * for the shuffling if it hs not calculated yet. Returns null if a build has + * not been queued nor a shuffling was calculated. + */ + get(epoch: Epoch, decisionRoot: RootHex): Promise; + + /** + * Queue asynchronous build for an EpochShuffling + */ + build(epoch: Epoch, decisionRoot: RootHex, state: BeaconStateAllForks, activeIndices: Uint32Array): void; +} /** * Readonly interface for EpochShuffling. @@ -58,32 +103,15 @@ export function computeCommitteeCount(activeValidatorCount: number): number { return Math.max(1, Math.min(MAX_COMMITTEES_PER_SLOT, committeesPerSlot)); } -export function computeEpochShuffling( - state: BeaconStateAllForks, - activeIndices: ArrayLike, - activeValidatorCount: number, - epoch: Epoch -): EpochShuffling { - const seed = getSeed(state, epoch, DOMAIN_BEACON_ATTESTER); - - if (activeValidatorCount > activeIndices.length) { - throw new Error(`Invalid activeValidatorCount: ${activeValidatorCount} > ${activeIndices.length}`); - } - // only the first `activeValidatorCount` elements are copied to `activeIndices` - const _activeIndices = new Uint32Array(activeValidatorCount); - for (let i = 0; i < activeValidatorCount; i++) { - _activeIndices[i] = activeIndices[i]; - } - const shuffling = _activeIndices.slice(); - unshuffleList(shuffling, seed); - +function buildCommitteesFromShuffling(shuffling: Uint32Array): Uint32Array[][] { + const activeValidatorCount = shuffling.length; const committeesPerSlot = computeCommitteeCount(activeValidatorCount); - const committeeCount = committeesPerSlot * SLOTS_PER_EPOCH; - const committees: Uint32Array[][] = []; + const committees = new Array(SLOTS_PER_EPOCH); for (let slot = 0; slot < SLOTS_PER_EPOCH; slot++) { - const slotCommittees: Uint32Array[] = []; + const slotCommittees = new Array(committeesPerSlot); + for (let committeeIndex = 0; committeeIndex < committeesPerSlot; committeeIndex++) { const index = slot * committeesPerSlot + committeeIndex; const startOffset = Math.floor((activeValidatorCount * index) / committeeCount); @@ -91,21 +119,67 @@ export function computeEpochShuffling( if (!(startOffset <= endOffset)) { throw new Error(`Invalid offsets: start ${startOffset} must be less than or equal end ${endOffset}`); } - slotCommittees.push(shuffling.subarray(startOffset, endOffset)); + slotCommittees[committeeIndex] = shuffling.subarray(startOffset, endOffset); } - committees.push(slotCommittees); + + committees[slot] = slotCommittees; } + return committees; +} + +export function computeEpochShuffling( + // TODO: (@matthewkeil) remove state/epoch and pass in seed to clean this up + state: BeaconStateAllForks, + activeIndices: Uint32Array, + epoch: Epoch +): EpochShuffling { + const seed = getSeed(state, epoch, DOMAIN_BEACON_ATTESTER); + const shuffling = unshuffleList(activeIndices, seed, SHUFFLE_ROUND_COUNT); + const committees = buildCommitteesFromShuffling(shuffling); return { epoch, - activeIndices: _activeIndices, + activeIndices, shuffling, committees, - committeesPerSlot, + committeesPerSlot: committees[0].length, }; } -export function getShufflingDecisionBlock(state: BeaconStateAllForks, epoch: Epoch): RootHex { +export async function computeEpochShufflingAsync( + // TODO: (@matthewkeil) remove state/epoch and pass in seed to clean this up + state: BeaconStateAllForks, + activeIndices: Uint32Array, + epoch: Epoch +): Promise { + const seed = getSeed(state, epoch, DOMAIN_BEACON_ATTESTER); + const shuffling = await asyncUnshuffleList(activeIndices, seed, SHUFFLE_ROUND_COUNT); + const committees = buildCommitteesFromShuffling(shuffling); + return { + epoch, + activeIndices, + shuffling, + committees, + committeesPerSlot: committees[0].length, + }; +} + +function calculateDecisionRoot(state: BeaconStateAllForks, epoch: Epoch): RootHex { const pivotSlot = computeStartSlotAtEpoch(epoch - 1) - 1; return toRootHex(getBlockRootAtSlot(state, pivotSlot)); } + +/** + * Get the shuffling decision block root for the given epoch of given state + * - Special case close to genesis block, return the genesis block root + * - This is similar to forkchoice.getDependentRoot() function, otherwise we cannot get cached shuffing in attestation verification when syncing from genesis. + */ +export function calculateShufflingDecisionRoot( + config: BeaconConfig, + state: BeaconStateAllForks, + epoch: Epoch +): RootHex { + return state.slot > GENESIS_SLOT + ? calculateDecisionRoot(state, epoch) + : toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(computeAnchorCheckpoint(config, state).blockHeader)); +} diff --git a/packages/state-transition/src/util/execution.ts b/packages/state-transition/src/util/execution.ts index 06e654f9f1d2..deed64bd6c51 100644 --- a/packages/state-transition/src/util/execution.ts +++ b/packages/state-transition/src/util/execution.ts @@ -2,7 +2,6 @@ import { bellatrix, capella, deneb, - electra, isBlindedBeaconBlockBody, ssz, BeaconBlock, @@ -72,13 +71,13 @@ export function isMergeTransitionComplete(state: BeaconStateExecutions): boolean // TODO: Performance ssz.bellatrix.ExecutionPayloadHeader.defaultValue() ); - } else { - return !ssz.capella.ExecutionPayloadHeader.equals( - state.latestExecutionPayloadHeader, - // TODO: Performance - ssz.capella.ExecutionPayloadHeader.defaultValue() - ); } + + return !ssz.capella.ExecutionPayloadHeader.equals( + state.latestExecutionPayloadHeader, + // TODO: Performance + ssz.capella.ExecutionPayloadHeader.defaultValue() + ); } /** Type guard for bellatrix.BeaconState */ @@ -113,11 +112,13 @@ export function getFullOrBlindedPayloadFromBody( ): ExecutionPayload | ExecutionPayloadHeader { if (isBlindedBeaconBlockBody(body)) { return body.executionPayloadHeader; - } else if ((body as bellatrix.BeaconBlockBody).executionPayload !== undefined) { + } + + if ((body as bellatrix.BeaconBlockBody).executionPayload !== undefined) { return (body as bellatrix.BeaconBlockBody).executionPayload; - } else { - throw Error("Not full or blinded beacon block"); } + + throw Error("Not full or blinded beacon block"); } export function isCapellaPayload( @@ -171,14 +172,7 @@ export function executionPayloadToPayloadHeader(fork: ForkSeq, payload: Executio ).excessBlobGas; } - if (fork >= ForkSeq.electra) { - (bellatrixPayloadFields as electra.ExecutionPayloadHeader).depositRequestsRoot = - ssz.electra.DepositRequests.hashTreeRoot((payload as electra.ExecutionPayload).depositRequests); - (bellatrixPayloadFields as electra.ExecutionPayloadHeader).withdrawalRequestsRoot = - ssz.electra.WithdrawalRequests.hashTreeRoot((payload as electra.ExecutionPayload).withdrawalRequests); - (bellatrixPayloadFields as electra.ExecutionPayloadHeader).consolidationRequestsRoot = - ssz.electra.ConsolidationRequests.hashTreeRoot((payload as electra.ExecutionPayload).consolidationRequests); - } + // No change in Electra return bellatrixPayloadFields; } diff --git a/packages/state-transition/src/util/genesis.ts b/packages/state-transition/src/util/genesis.ts index 54507d0ef235..aca81258a47a 100644 --- a/packages/state-transition/src/util/genesis.ts +++ b/packages/state-transition/src/util/genesis.ts @@ -171,10 +171,15 @@ export function applyDeposits( if (fork >= ForkSeq.electra) { const stateElectra = state as CachedBeaconStateElectra; stateElectra.commit(); - for (const {index: validatorIndex, amount} of stateElectra.pendingBalanceDeposits.getAllReadonly()) { - increaseBalance(state, validatorIndex, Number(amount)); + for (const {pubkey, amount} of stateElectra.pendingDeposits.getAllReadonly()) { + const validatorIndex = state.epochCtx.getValidatorIndex(pubkey); + if (validatorIndex === null) { + // Should not happen if the gensis state is correct + continue; + } + increaseBalance(state, validatorIndex, amount); } - stateElectra.pendingBalanceDeposits = ssz.electra.PendingBalanceDeposits.defaultViewDU(); + stateElectra.pendingDeposits = ssz.electra.PendingDeposits.defaultViewDU(); } // Process activations diff --git a/packages/state-transition/src/util/index.ts b/packages/state-transition/src/util/index.ts index 9b9916f1d49e..b88a20719b85 100644 --- a/packages/state-transition/src/util/index.ts +++ b/packages/state-transition/src/util/index.ts @@ -4,7 +4,9 @@ export * from "./attestation.js"; export * from "./attesterStatus.js"; export * from "./balance.js"; export * from "./blindedBlock.js"; +export * from "./calculateCommitteeAssignments.js"; export * from "./capella.js"; +export * from "./computeAnchorCheckpoint.js"; export * from "./execution.js"; export * from "./blockRoot.js"; export * from "./domain.js"; @@ -15,7 +17,6 @@ export * from "./genesis.js"; export * from "./interop.js"; export * from "./rootCache.js"; export * from "./seed.js"; -export * from "./shuffle.js"; export * from "./shufflingDecisionRoot.js"; export * from "./signatureSets.js"; export * from "./signingRoot.js"; diff --git a/packages/state-transition/src/util/loadState/loadValidator.ts b/packages/state-transition/src/util/loadState/loadValidator.ts index dcf5051c9c6d..406c848ef089 100644 --- a/packages/state-transition/src/util/loadState/loadValidator.ts +++ b/packages/state-transition/src/util/loadState/loadValidator.ts @@ -17,9 +17,8 @@ export function loadValidator( newValidatorValue[field] = seedValidator[field]; } return ssz.phase0.Validator.toViewDU(newValidatorValue); - } else { - return ssz.phase0.Validator.deserializeToViewDU(newValidatorBytes); } + return ssz.phase0.Validator.deserializeToViewDU(newValidatorBytes); } /** diff --git a/packages/state-transition/src/util/seed.ts b/packages/state-transition/src/util/seed.ts index a5a0028d6c17..4131d4d9481f 100644 --- a/packages/state-transition/src/util/seed.ts +++ b/packages/state-transition/src/util/seed.ts @@ -155,7 +155,7 @@ export function computeShuffledIndex(index: number, indexCount: number, seed: By const position = Math.max(permuted, flip); const source = digest(Buffer.concat([_seed, intToBytes(i, 1), intToBytes(Math.floor(position / 256), 4)])); const byte = source[Math.floor((position % 256) / 8)]; - const bit = (byte >> position % 8) % 2; + const bit = (byte >> (position % 8)) % 2; permuted = bit ? flip : permuted; } return permuted; diff --git a/packages/state-transition/src/util/shuffle.ts b/packages/state-transition/src/util/shuffle.ts deleted file mode 100644 index 39137bca69d0..000000000000 --- a/packages/state-transition/src/util/shuffle.ts +++ /dev/null @@ -1,222 +0,0 @@ -import {digest} from "@chainsafe/as-sha256"; -import {SHUFFLE_ROUND_COUNT} from "@lodestar/params"; -import {Bytes32} from "@lodestar/types"; -import {assert, bytesToBigInt} from "@lodestar/utils"; - -// ArrayLike but with settable indices -type Shuffleable = { - readonly length: number; - [index: number]: number; -}; - -// ShuffleList shuffles a list, using the given seed for randomness. Mutates the input list. -export function shuffleList(input: Shuffleable, seed: Bytes32): void { - innerShuffleList(input, seed, true); -} - -// UnshuffleList undoes a list shuffling using the seed of the shuffling. Mutates the input list. -export function unshuffleList(input: Shuffleable, seed: Bytes32): void { - innerShuffleList(input, seed, false); -} - -const _SHUFFLE_H_SEED_SIZE = 32; -const _SHUFFLE_H_ROUND_SIZE = 1; -const _SHUFFLE_H_POSITION_WINDOW_SIZE = 4; -const _SHUFFLE_H_PIVOT_VIEW_SIZE = _SHUFFLE_H_SEED_SIZE + _SHUFFLE_H_ROUND_SIZE; -const _SHUFFLE_H_TOTAL_SIZE = _SHUFFLE_H_SEED_SIZE + _SHUFFLE_H_ROUND_SIZE + _SHUFFLE_H_POSITION_WINDOW_SIZE; - -/* - -def shuffle(list_size, seed): - indices = list(range(list_size)) - for round in range(90): - hash_bytes = b''.join([ - hash(seed + round.to_bytes(1, 'little') + (i).to_bytes(4, 'little')) - for i in range((list_size + 255) // 256) - ]) - pivot = int.from_bytes(hash(seed + round.to_bytes(1, 'little')), 'little') % list_size - - powers_of_two = [1, 2, 4, 8, 16, 32, 64, 128] - - for i, index in enumerate(indices): - flip = (pivot - index) % list_size - hash_pos = index if index > flip else flip - byte = hash_bytes[hash_pos // 8] - if byte & powers_of_two[hash_pos % 8]: - indices[i] = flip - return indices - -Heavily-optimized version of the set-shuffling algorithm proposed by Vitalik to shuffle all items in a list together. - -Original here: - https://github.com/ethereum/consensus-specs/pull/576 - -Main differences, implemented by @protolambda: - - User can supply input slice to shuffle, simple provide [0,1,2,3,4, ...] to get a list of cleanly shuffled indices. - - Input slice is shuffled (hence no return value), no new array is allocated - - Allocations as minimal as possible: only a very minimal buffer for hashing - (this should be allocated on the stack, compiler will find it with escape analysis). - This is not bigger than what's used for shuffling a single index! - As opposed to larger allocations (size O(n) instead of O(1)) made in the original. - - Replaced pseudocode/python workarounds with bit-logic. - - User can provide their own hash-function (as long as it outputs a 32 len byte slice) - -This Typescript version is an adaption of the Python version, in turn an adaption of the original Go version. -Python: https://github.com/protolambda/eth2fastspec/blob/14e04e9db77ef7c8b7788ffdaa7e142d7318dd7e/eth2fastspec.py#L63 -Go: https://github.com/protolambda/eth2-shuffle -All three implemented by @protolambda, but meant for public use, like the original spec version. -*/ - -function setPositionUint32(value: number, buf: Buffer): void { - // Little endian, optimized version - buf[_SHUFFLE_H_PIVOT_VIEW_SIZE] = (value >> 0) & 0xff; - buf[_SHUFFLE_H_PIVOT_VIEW_SIZE + 1] = (value >> 8) & 0xff; - buf[_SHUFFLE_H_PIVOT_VIEW_SIZE + 2] = (value >> 16) & 0xff; - buf[_SHUFFLE_H_PIVOT_VIEW_SIZE + 3] = (value >> 24) & 0xff; -} - -// Shuffles or unshuffles, depending on the `dir` (true for shuffling, false for unshuffling -function innerShuffleList(input: Shuffleable, seed: Bytes32, dir: boolean): void { - if (input.length <= 1) { - // nothing to (un)shuffle - return; - } - if (SHUFFLE_ROUND_COUNT == 0) { - // no shuffling - return; - } - // uint32 is sufficient, and necessary in JS, - // as we do a lot of bit math on it, which cannot be done as fast on more bits. - const listSize = input.length >>> 0; - // check if list size fits in uint32 - assert.equal(listSize, input.length, "input length does not fit uint32"); - // check that the seed is 32 bytes - assert.lte(seed.length, _SHUFFLE_H_SEED_SIZE, `seed length is not lte ${_SHUFFLE_H_SEED_SIZE} bytes`); - - const buf = Buffer.alloc(_SHUFFLE_H_TOTAL_SIZE); - let r = 0; - if (!dir) { - // Start at last round. - // Iterating through the rounds in reverse, un-swaps everything, effectively un-shuffling the list. - r = SHUFFLE_ROUND_COUNT - 1; - } - - // Seed is always the first 32 bytes of the hash input, we never have to change this part of the buffer. - buf.set(seed, 0); - - // initial values here are not used: overwritten first within the inner for loop. - let source = seed; // just setting it to a Bytes32 - let byteV = 0; - - // eslint-disable-next-line no-constant-condition - while (true) { - // spec: pivot = bytes_to_int(hash(seed + int_to_bytes1(round))[0:8]) % list_size - // This is the "int_to_bytes1(round)", appended to the seed. - buf[_SHUFFLE_H_SEED_SIZE] = r; - // Seed is already in place, now just hash the correct part of the buffer, and take a uint64 from it, - // and modulo it to get a pivot within range. - const h = digest(buf.subarray(0, _SHUFFLE_H_PIVOT_VIEW_SIZE)); - const pivot = Number(bytesToBigInt(h.subarray(0, 8)) % BigInt(listSize)) >>> 0; - - // Split up the for-loop in two: - // 1. Handle the part from 0 (incl) to pivot (incl). This is mirrored around (pivot / 2) - // 2. Handle the part from pivot (excl) to N (excl). This is mirrored around ((pivot / 2) + (size/2)) - // The pivot defines a split in the array, with each of the splits mirroring their data within the split. - // Print out some example even/odd sized index lists, with some even/odd pivots, - // and you can deduce how the mirroring works exactly. - // Note that the mirror is strict enough to not consider swapping the index @mirror with itself. - let mirror = (pivot + 1) >> 1; - // Since we are iterating through the "positions" in order, we can just repeat the hash every 256th position. - // No need to pre-compute every possible hash for efficiency like in the example code. - // We only need it consecutively (we are going through each in reverse order however, but same thing) - // - // spec: source = hash(seed + int_to_bytes1(round) + int_to_bytes4(position // 256)) - // - seed is still in 0:32 (excl., 32 bytes) - // - round number is still in 32 - // - mix in the position for randomness, except the last byte of it, - // which will be used later to select a bit from the resulting hash. - // We start from the pivot position, and work back to the mirror position (of the part left to the pivot). - // This makes us process each pear exactly once (instead of unnecessarily twice, like in the spec) - setPositionUint32(pivot >> 8, buf); // already using first pivot byte below. - source = digest(buf); - byteV = source[(pivot & 0xff) >> 3]; - - for (let i = 0, j; i < mirror; i++) { - j = pivot - i; - // -- step() fn start - // The pair is i,j. With j being the bigger of the two, hence the "position" identifier of the pair. - // Every 256th bit (aligned to j). - if ((j & 0xff) == 0xff) { - // just overwrite the last part of the buffer, reuse the start (seed, round) - setPositionUint32(j >> 8, buf); - source = digest(buf); - } - - // Same trick with byte retrieval. Only every 8th. - if ((j & 0x7) == 0x7) { - byteV = source[(j & 0xff) >> 3]; - } - - const bitV = (byteV >> (j & 0x7)) & 0x1; - - if (bitV == 1) { - // swap the pair items - const tmp = input[j]; - input[j] = input[i]; - input[i] = tmp; - } - // -- step() fn end - } - - // Now repeat, but for the part after the pivot. - mirror = (pivot + listSize + 1) >> 1; - const end = listSize - 1; - // Again, seed and round input is in place, just update the position. - // We start at the end, and work back to the mirror point. - // This makes us process each pear exactly once (instead of unnecessarily twice, like in the spec) - setPositionUint32(end >> 8, buf); - source = digest(buf); - byteV = source[(end & 0xff) >> 3]; - for (let i = pivot + 1, j; i < mirror; i++) { - j = end - i + pivot + 1; - // -- step() fn start - // The pair is i,j. With j being the bigger of the two, hence the "position" identifier of the pair. - // Every 256th bit (aligned to j). - if ((j & 0xff) == 0xff) { - // just overwrite the last part of the buffer, reuse the start (seed, round) - setPositionUint32(j >> 8, buf); - source = digest(buf); - } - - // Same trick with byte retrieval. Only every 8th. - if ((j & 0x7) == 0x7) { - byteV = source[(j & 0xff) >> 3]; - } - - const bitV = (byteV >> (j & 0x7)) & 0x1; - - if (bitV == 1) { - // swap the pair items - const tmp = input[j]; - input[j] = input[i]; - input[i] = tmp; - } - // -- step() fn end - } - - // go forwards? - if (dir) { - // -> shuffle - r += 1; - if (r == SHUFFLE_ROUND_COUNT) { - break; - } - } else { - if (r == 0) { - break; - } - // -> un-shuffle - r -= 1; - } - } -} diff --git a/packages/state-transition/src/util/shufflingDecisionRoot.ts b/packages/state-transition/src/util/shufflingDecisionRoot.ts index 10af814e9af3..5a6c500d3987 100644 --- a/packages/state-transition/src/util/shufflingDecisionRoot.ts +++ b/packages/state-transition/src/util/shufflingDecisionRoot.ts @@ -12,11 +12,10 @@ import {computeStartSlotAtEpoch} from "./epoch.js"; */ export function proposerShufflingDecisionRoot(state: CachedBeaconStateAllForks): Root | null { const decisionSlot = proposerShufflingDecisionSlot(state); - if (state.slot == decisionSlot) { + if (state.slot === decisionSlot) { return null; - } else { - return getBlockRootAtSlot(state, decisionSlot); } + return getBlockRootAtSlot(state, decisionSlot); } /** @@ -37,11 +36,10 @@ function proposerShufflingDecisionSlot(state: CachedBeaconStateAllForks): Slot { */ export function attesterShufflingDecisionRoot(state: CachedBeaconStateAllForks, requestedEpoch: Epoch): Root | null { const decisionSlot = attesterShufflingDecisionSlot(state, requestedEpoch); - if (state.slot == decisionSlot) { + if (state.slot === decisionSlot) { return null; - } else { - return getBlockRootAtSlot(state, decisionSlot); } + return getBlockRootAtSlot(state, decisionSlot); } /** @@ -75,7 +73,6 @@ function attesterShufflingDecisionEpoch(state: CachedBeaconStateAllForks, reques if (requestedEpoch < currentEpoch) { throw Error(`EpochTooLow: current ${currentEpoch} requested ${requestedEpoch}`); - } else { - throw Error(`EpochTooHigh: current ${currentEpoch} requested ${requestedEpoch}`); } + throw Error(`EpochTooHigh: current ${currentEpoch} requested ${requestedEpoch}`); } diff --git a/packages/state-transition/src/util/validator.ts b/packages/state-transition/src/util/validator.ts index ebad21d9d25c..4906a9349402 100644 --- a/packages/state-transition/src/util/validator.ts +++ b/packages/state-transition/src/util/validator.ts @@ -45,9 +45,8 @@ export function getActiveValidatorIndices(state: BeaconStateAllForks, epoch: Epo export function getActivationChurnLimit(config: ChainForkConfig, fork: ForkSeq, activeValidatorCount: number): number { if (fork >= ForkSeq.deneb) { return Math.min(config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT, getChurnLimit(config, activeValidatorCount)); - } else { - return getChurnLimit(config, activeValidatorCount); } + return getChurnLimit(config, activeValidatorCount); } export function getChurnLimit(config: ChainForkConfig, activeValidatorCount: number): number { @@ -79,17 +78,8 @@ export function getMaxEffectiveBalance(withdrawalCredentials: Uint8Array): numbe // Compounding withdrawal credential only available since Electra if (hasCompoundingWithdrawalCredential(withdrawalCredentials)) { return MAX_EFFECTIVE_BALANCE_ELECTRA; - } else { - return MIN_ACTIVATION_BALANCE; } -} - -export function getActiveBalance(state: CachedBeaconStateElectra, validatorIndex: ValidatorIndex): number { - const validatorMaxEffectiveBalance = getMaxEffectiveBalance( - state.validators.getReadonly(validatorIndex).withdrawalCredentials - ); - - return Math.min(state.balances.get(validatorIndex), validatorMaxEffectiveBalance); + return MIN_ACTIVATION_BALANCE; } export function getPendingBalanceToWithdraw(state: CachedBeaconStateElectra, validatorIndex: ValidatorIndex): number { diff --git a/packages/state-transition/src/util/weakSubjectivity.ts b/packages/state-transition/src/util/weakSubjectivity.ts index 0e606e66340f..8d7c82842496 100644 --- a/packages/state-transition/src/util/weakSubjectivity.ts +++ b/packages/state-transition/src/util/weakSubjectivity.ts @@ -80,7 +80,6 @@ export function computeWeakSubjectivityPeriodFromConstituents( const t = Math.floor(totalBalanceByIncrement / N); const T = MAX_EFFECTIVE_BALANCE / ETH_TO_GWEI; const delta = churnLimit; - // eslint-disable-next-line @typescript-eslint/naming-convention const Delta = MAX_DEPOSITS * SLOTS_PER_EPOCH; const D = SAFETY_DECAY; diff --git a/packages/state-transition/test/cache.ts b/packages/state-transition/test/cache.ts index 9ca7ea25201c..59ddfe40b7c1 100644 --- a/packages/state-transition/test/cache.ts +++ b/packages/state-transition/test/cache.ts @@ -3,7 +3,6 @@ import {fileURLToPath} from "node:url"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const testCachePath = path.join(__dirname, "../test-cache"); diff --git a/packages/state-transition/test/perf/analyzeEpochs.ts b/packages/state-transition/test/perf/analyzeEpochs.ts index deb0861427bf..ebad63ccf0ad 100644 --- a/packages/state-transition/test/perf/analyzeEpochs.ts +++ b/packages/state-transition/test/perf/analyzeEpochs.ts @@ -123,7 +123,6 @@ async function analyzeEpochs(network: NetworkName, fromEpoch?: number): Promise< const {previousEpochAttestations, currentEpochAttestations} = state as phase0.BeaconState; - // eslint-disable-next-line no-console console.log(`Processed epoch ${epoch}`); writeToCsv({ epoch, @@ -153,7 +152,7 @@ async function analyzeEpochs(network: NetworkName, fromEpoch?: number): Promise< // processSlashingsAllForks: function of process.indicesToSlash // processSlashingsReset: free // -- electra - // processPendingBalanceDeposits: - + // processPendingDeposits: - // processPendingConsolidations: - // -- altair // processInactivityUpdates: - @@ -182,7 +181,6 @@ if (!network) { } analyzeEpochs(network as NetworkName, fromEpoch).catch((e: Error) => { - // eslint-disable-next-line no-console console.error(e); process.exit(1); }); diff --git a/packages/state-transition/test/perf/csv.ts b/packages/state-transition/test/perf/csv.ts index d54b277d707c..2f47fd79f05d 100644 --- a/packages/state-transition/test/perf/csv.ts +++ b/packages/state-transition/test/perf/csv.ts @@ -41,7 +41,7 @@ export function readCsv>(filepath: string): T[] for (let i = 0; i < headers.length; i++) { const str = valuesRow[i]; const num = parseInt(str); - value[headers[i] as keyof T] = (isNaN(num) ? str : num) as T[keyof T]; + value[headers[i] as keyof T] = (Number.isNaN(num) ? str : num) as T[keyof T]; } values[j] = value; } diff --git a/packages/state-transition/test/perf/dataStructures/arrayish.memory.ts b/packages/state-transition/test/perf/dataStructures/arrayish.memory.ts index 7e10f447181f..f37421ce2a89 100644 --- a/packages/state-transition/test/perf/dataStructures/arrayish.memory.ts +++ b/packages/state-transition/test/perf/dataStructures/arrayish.memory.ts @@ -110,7 +110,6 @@ for (let i = 0; i < 1e8; i++) { const heapUsedM = linearRegression(xs, heapUsed).m; const rssM = linearRegression(xs, rss).m; - // eslint-disable-next-line no-console console.log(i, {arrayBuffersM, externalM, heapTotalM, heapUsedM, rssM}); } } diff --git a/packages/state-transition/test/perf/dataStructures/arrayish.test.ts b/packages/state-transition/test/perf/dataStructures/arrayish.test.ts index 5b6af0d989b6..353f81951aa7 100644 --- a/packages/state-transition/test/perf/dataStructures/arrayish.test.ts +++ b/packages/state-transition/test/perf/dataStructures/arrayish.test.ts @@ -104,7 +104,7 @@ describe("Array", () => { let arr: number[]; - before(function () { + before(() => { arr = createArray(n); }); diff --git a/packages/state-transition/test/perf/epoch/epochAltair.test.ts b/packages/state-transition/test/perf/epoch/epochAltair.test.ts index 15cde849ce9f..5a10fd4d8bbd 100644 --- a/packages/state-transition/test/perf/epoch/epochAltair.test.ts +++ b/packages/state-transition/test/perf/epoch/epochAltair.test.ts @@ -46,6 +46,7 @@ describe(`altair processEpoch - ${stateId}`, () => { fn: (state) => { const cache = beforeProcessEpoch(state); processEpoch(fork, state as CachedBeaconStateAltair, cache); + state.slot++; state.epochCtx.afterProcessEpoch(state, cache); // Simulate root computation through the next block to account for changes // 74184 hash64 ops - 92.730 ms @@ -187,6 +188,9 @@ function benchmarkAltairEpochSteps(stateOg: LazyValue return {state, cache: cacheAfter}; }, beforeEach: ({state, cache}) => ({state: state.clone(), cache}), - fn: ({state, cache}) => state.epochCtx.afterProcessEpoch(state, cache), + fn: ({state, cache}) => { + state.slot++; + state.epochCtx.afterProcessEpoch(state, cache); + }, }); } diff --git a/packages/state-transition/test/perf/epoch/epochCapella.test.ts b/packages/state-transition/test/perf/epoch/epochCapella.test.ts index 61bfad20b1ee..a4daf308aaa0 100644 --- a/packages/state-transition/test/perf/epoch/epochCapella.test.ts +++ b/packages/state-transition/test/perf/epoch/epochCapella.test.ts @@ -46,6 +46,7 @@ describe(`capella processEpoch - ${stateId}`, () => { fn: (state) => { const cache = beforeProcessEpoch(state); processEpoch(fork, state as CachedBeaconStateCapella, cache); + state.slot++; state.epochCtx.afterProcessEpoch(state, cache); // Simulate root computation through the next block to account for changes // 74184 hash64 ops - 92.730 ms @@ -159,6 +160,9 @@ function benchmarkAltairEpochSteps(stateOg: LazyValue return {state, cache: cacheAfter}; }, beforeEach: ({state, cache}) => ({state: state.clone(), cache}), - fn: ({state, cache}) => state.epochCtx.afterProcessEpoch(state, cache), + fn: ({state, cache}) => { + state.slot++; + state.epochCtx.afterProcessEpoch(state, cache); + }, }); } diff --git a/packages/state-transition/test/perf/epoch/epochPhase0.test.ts b/packages/state-transition/test/perf/epoch/epochPhase0.test.ts index 3af3d4d4a832..5c19b347af62 100644 --- a/packages/state-transition/test/perf/epoch/epochPhase0.test.ts +++ b/packages/state-transition/test/perf/epoch/epochPhase0.test.ts @@ -43,6 +43,7 @@ describe(`phase0 processEpoch - ${stateId}`, () => { fn: (state) => { const cache = beforeProcessEpoch(state); processEpoch(fork, state as CachedBeaconStatePhase0, cache); + state.slot++; state.epochCtx.afterProcessEpoch(state, cache); // Simulate root computation through the next block to account for changes state.hashTreeRoot(); @@ -162,6 +163,9 @@ function benchmarkPhase0EpochSteps(stateOg: LazyValue return {state, cache: cacheAfter}; }, beforeEach: ({state, cache}) => ({state: state.clone(), cache}), - fn: ({state, cache}) => state.epochCtx.afterProcessEpoch(state, cache), + fn: ({state, cache}) => { + state.slot++; + state.epochCtx.afterProcessEpoch(state, cache); + }, }); } diff --git a/packages/state-transition/test/perf/epoch/processRegistryUpdates.test.ts b/packages/state-transition/test/perf/epoch/processRegistryUpdates.test.ts index 2d57de44f8ee..588fe9ec0213 100644 --- a/packages/state-transition/test/perf/epoch/processRegistryUpdates.test.ts +++ b/packages/state-transition/test/perf/epoch/processRegistryUpdates.test.ts @@ -78,7 +78,7 @@ type IndicesLengths = { * Create a state that causes `changeRatio` fraction (0,1) of validators to change their effective balance. */ function getRegistryUpdatesTestData( - vc: number, + _vc: number, lengths: IndicesLengths ): { state: CachedBeaconStateAllForks; diff --git a/packages/state-transition/test/perf/hashing.test.ts b/packages/state-transition/test/perf/hashing.test.ts index b7afc59717bc..26bfd935c08a 100644 --- a/packages/state-transition/test/perf/hashing.test.ts +++ b/packages/state-transition/test/perf/hashing.test.ts @@ -1,13 +1,14 @@ import {itBench} from "@dapplion/benchmark"; +import {unshuffleList} from "@chainsafe/swap-or-not-shuffle"; import {ssz} from "@lodestar/types"; -import {unshuffleList} from "../../src/index.js"; +import {SHUFFLE_ROUND_COUNT} from "@lodestar/params"; import {generatePerfTestCachedStatePhase0, numValidators} from "./util.js"; // Test cost of hashing state after some modifications describe("BeaconState hashTreeRoot", () => { const vc = numValidators; - const indicesShuffled: number[] = []; + let indicesShuffled: Uint32Array; let stateOg: ReturnType; before(function () { @@ -15,8 +16,13 @@ describe("BeaconState hashTreeRoot", () => { stateOg = generatePerfTestCachedStatePhase0(); stateOg.hashTreeRoot(); - for (let i = 0; i < vc; i++) indicesShuffled[i] = i; - unshuffleList(indicesShuffled, new Uint8Array([42, 32])); + const seed = new Uint8Array(32); + seed.set([42, 32], 0); + const preShuffle = new Uint32Array(numValidators); + for (let i = 0; i < vc; i++) { + preShuffle[i] = i; + } + indicesShuffled = unshuffleList(preShuffle, seed, SHUFFLE_ROUND_COUNT); }); const validator = ssz.phase0.Validator.defaultViewDU(); diff --git a/packages/state-transition/test/perf/misc/aggregationBits.test.ts b/packages/state-transition/test/perf/misc/aggregationBits.test.ts index 7954f6a06a71..a0578970dfe9 100644 --- a/packages/state-transition/test/perf/misc/aggregationBits.test.ts +++ b/packages/state-transition/test/perf/misc/aggregationBits.test.ts @@ -12,7 +12,7 @@ describe("aggregationBits", () => { let indexes: number[]; let bitlistTree: BitArray; - before(function () { + before(() => { const aggregationBits = BitArray.fromBoolArray(Array.from({length: len}, () => true)); bitlistTree = ssz.phase0.CommitteeBits.toViewDU(aggregationBits); indexes = Array.from({length: len}, () => 165432); diff --git a/packages/state-transition/test/perf/misc/arrayCreation.test.ts b/packages/state-transition/test/perf/misc/arrayCreation.test.ts index 8fd52ff99e27..4568948c678d 100644 --- a/packages/state-transition/test/perf/misc/arrayCreation.test.ts +++ b/packages/state-transition/test/perf/misc/arrayCreation.test.ts @@ -1,4 +1,4 @@ -describe.skip("array creation", function () { +describe.skip("array creation", () => { const testCases: {id: string; fn: (n: number) => void}[] = [ { id: "Array.from(() => 0)", @@ -36,7 +36,6 @@ describe.skip("array creation", function () { } const to = process.hrtime.bigint(); const diffMs = Number(to - from) / 1e6; - // eslint-disable-next-line no-console console.log(`${id}: ${diffMs / opsRun} ms`); }); } diff --git a/packages/state-transition/test/perf/misc/bitopts.test.ts b/packages/state-transition/test/perf/misc/bitopts.test.ts index 93df681bb3e2..38677002a969 100644 --- a/packages/state-transition/test/perf/misc/bitopts.test.ts +++ b/packages/state-transition/test/perf/misc/bitopts.test.ts @@ -14,7 +14,6 @@ describe.skip("bit opts", function () { } const to = process.hrtime.bigint(); const diffMs = Number(to - from) / 1e6; - // eslint-disable-next-line no-console console.log(`Time spent on OR in getAttestationDeltas: ${diffMs * ((orOptsPerRun * validators) / opsRun)} ms`); }); }); diff --git a/packages/state-transition/test/perf/misc/proxy.test.ts b/packages/state-transition/test/perf/misc/proxy.test.ts index 6a3536a12de5..08618a1727e2 100644 --- a/packages/state-transition/test/perf/misc/proxy.test.ts +++ b/packages/state-transition/test/perf/misc/proxy.test.ts @@ -12,9 +12,8 @@ describe("Proxy cost", () => { get(target, p) { if (p === "length") { return target.length; - } else { - return target[p as unknown as number]; } + return target[p as unknown as number]; }, }); diff --git a/packages/state-transition/test/perf/shuffle/numberMath.test.ts b/packages/state-transition/test/perf/shuffle/numberMath.test.ts deleted file mode 100644 index 08e57b090aa3..000000000000 --- a/packages/state-transition/test/perf/shuffle/numberMath.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import {itBench} from "@dapplion/benchmark"; - -describe.skip("shuffle number math ops", () => { - const forRuns = 100e5; - const j = forRuns / 2; - - const arrSize = forRuns; - const input: number[] = []; - const inputUint32Array = new Uint32Array(arrSize); - for (let i = 0; i < arrSize; i++) { - input[i] = i; - inputUint32Array[i] = i; - } - - itBench("if(i == 1)", () => { - for (let i = 0; i < forRuns; i++) { - if (i === 1) { - // - } - } - }); - - itBench("if(i)", () => { - for (let i = 0; i < forRuns; i++) { - if (i) { - // - } - } - }); - - itBench("i == j", () => { - for (let i = 0; i < forRuns; i++) { - i == j; - } - }); - - itBench("i === j", () => { - for (let i = 0; i < forRuns; i++) { - i === j; - } - }); - - itBench("bit opts", () => { - for (let i = 0; i < forRuns; i++) { - (j & 0x7) == 0x7; - } - }); - - itBench("modulo", () => { - for (let i = 0; i < forRuns; i++) { - j % 8 == 0; - } - }); - - itBench(">> 3", () => { - for (let i = 0; i < forRuns; i++) { - j >> 3; - } - }); - - itBench("/ 8", () => { - for (let i = 0; i < forRuns; i++) { - j / 8; - } - }); - - itBench("swap item in array", () => { - for (let i = 0; i < forRuns; i++) { - const tmp = input[forRuns - i]; - input[forRuns - i] = input[i]; - input[i] = tmp; - } - }); - - itBench("swap item in Uint32Array", () => { - for (let i = 0; i < forRuns; i++) { - const tmp = inputUint32Array[forRuns - i]; - inputUint32Array[forRuns - i] = inputUint32Array[i]; - inputUint32Array[i] = tmp; - } - }); -}); diff --git a/packages/state-transition/test/perf/shuffle/shuffle.test.ts b/packages/state-transition/test/perf/shuffle/shuffle.test.ts deleted file mode 100644 index ea1a9d606184..000000000000 --- a/packages/state-transition/test/perf/shuffle/shuffle.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {itBench} from "@dapplion/benchmark"; -import {unshuffleList} from "../../../src/index.js"; - -// Lightouse Lodestar -// 512 254.04 us 1.6034 ms (x6) -// 16384 6.2046 ms 18.272 ms (x3) -// 4000000 1.5617 s 4.9690 s (x3) - -describe("shuffle list", () => { - const seed = new Uint8Array([42, 32]); - - for (const listSize of [ - 16384, 250000, - // Don't run 4_000_000 since it's very slow and not testnet has gotten there yet - // 4e6, - ]) { - itBench({ - id: `shuffle list - ${listSize} els`, - before: () => { - const input: number[] = []; - for (let i = 0; i < listSize; i++) input[i] = i; - return new Uint32Array(input); - }, - beforeEach: (input) => input, - fn: (input) => unshuffleList(input, seed), - }); - } -}); diff --git a/packages/state-transition/test/perf/util.ts b/packages/state-transition/test/perf/util.ts index f3c2eaef91e5..0f47c241f8f9 100644 --- a/packages/state-transition/test/perf/util.ts +++ b/packages/state-transition/test/perf/util.ts @@ -1,5 +1,6 @@ import {BitArray, fromHexString} from "@chainsafe/ssz"; import {PublicKey, SecretKey} from "@chainsafe/blst"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {phase0, ssz, Slot, BeaconState} from "@lodestar/types"; import {config} from "@lodestar/config/default"; import {createBeaconConfig, createChainForkConfig} from "@lodestar/config"; @@ -17,7 +18,6 @@ import { interopSecretKey, computeEpochAtSlot, getActiveValidatorIndices, - PubkeyIndexMap, newFilledArray, createCachedBeaconState, computeCommitteeCount, @@ -61,7 +61,6 @@ const secretKeyByModIndex = new Map(); const epoch = 23638; export const perfStateEpoch = epoch; -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function getPubkeys(vc = numValidators) { const pubkeysMod = interopPubkeysCached(keypairsMod); const pubkeysModObj = pubkeysMod.map((pk) => PublicKey.fromBytes(pk)); @@ -85,7 +84,6 @@ export function getSecretKeyFromIndexCached(validatorIndex: number): SecretKey { return sk; } -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type function getPubkeyCaches({pubkeysMod, pubkeysModObj}: ReturnType) { // Manually sync pubkeys to prevent doing BLS opts 110_000 times const pubkey2index = new PubkeyIndexMap(); @@ -196,7 +194,7 @@ export function generatePerfTestCachedStatePhase0(opts?: {goBackOneSlot: boolean ) as CachedBeaconStatePhase0; phase0CachedState23638.slot += 1; } - const resultingState = opts && opts.goBackOneSlot ? phase0CachedState23637 : phase0CachedState23638; + const resultingState = opts?.goBackOneSlot ? phase0CachedState23637 : phase0CachedState23638; return resultingState.clone(); } @@ -223,7 +221,6 @@ export function generatePerfTestCachedStateAltair(opts?: { const {pubkeys, pubkeysMod, pubkeysModObj} = getPubkeys(opts?.vc); const {pubkey2index, index2pubkey} = getPubkeyCaches({pubkeys, pubkeysMod, pubkeysModObj}); - // eslint-disable-next-line @typescript-eslint/naming-convention const altairConfig = createChainForkConfig({ALTAIR_FORK_EPOCH: 0}); const origState = generatePerformanceStateAltair(pubkeys); @@ -244,7 +241,7 @@ export function generatePerfTestCachedStateAltair(opts?: { ) as CachedBeaconStateAltair; altairCachedState23638.slot += 1; } - const resultingState = opts && opts.goBackOneSlot ? altairCachedState23637 : altairCachedState23638; + const resultingState = opts?.goBackOneSlot ? altairCachedState23637 : altairCachedState23638; return resultingState.clone(); } diff --git a/packages/state-transition/test/perf/util/loadState/loadState.test.ts b/packages/state-transition/test/perf/util/loadState/loadState.test.ts index a8a1b1399dc5..9f6175e95684 100644 --- a/packages/state-transition/test/perf/util/loadState/loadState.test.ts +++ b/packages/state-transition/test/perf/util/loadState/loadState.test.ts @@ -1,8 +1,9 @@ import {itBench, setBenchOpts} from "@dapplion/benchmark"; import {PublicKey} from "@chainsafe/blst"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {loadState} from "../../../../src/util/loadState/loadState.js"; import {createCachedBeaconState} from "../../../../src/cache/stateCache.js"; -import {Index2PubkeyCache, PubkeyIndexMap} from "../../../../src/cache/pubkeyCache.js"; +import {Index2PubkeyCache} from "../../../../src/cache/pubkeyCache.js"; import {generatePerfTestCachedStateAltair} from "../../util.js"; /** @@ -79,17 +80,15 @@ describe("loadState", function () { pubkey2index.set(pubkey, validatorIndex); index2pubkey[validatorIndex] = PublicKey.fromBytes(pubkey); } - // skip computimg shuffling in performance test because in reality we have a ShufflingCache - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const shufflingGetter = () => seedState.epochCtx.currentShuffling; createCachedBeaconState( migratedState, { config: seedState.config, pubkey2index, index2pubkey, + shufflingCache: seedState.epochCtx.shufflingCache, }, - {skipSyncPubkeys: true, skipSyncCommitteeCache: true, shufflingGetter} + {skipSyncPubkeys: true, skipSyncCommitteeCache: true} ); }, }); diff --git a/packages/state-transition/test/perf/util/shufflings.test.ts b/packages/state-transition/test/perf/util/shufflings.test.ts index 24be96c4676d..41767c184349 100644 --- a/packages/state-transition/test/perf/util/shufflings.test.ts +++ b/packages/state-transition/test/perf/util/shufflings.test.ts @@ -27,17 +27,17 @@ describe("epoch shufflings", () => { itBench({ id: `computeProposers - vc ${numValidators}`, fn: () => { - const epochSeed = getSeed(state, state.epochCtx.nextShuffling.epoch, DOMAIN_BEACON_PROPOSER); + const epochSeed = getSeed(state, state.epochCtx.epoch, DOMAIN_BEACON_PROPOSER); const fork = state.config.getForkSeq(state.slot); - computeProposers(fork, epochSeed, state.epochCtx.nextShuffling, state.epochCtx.effectiveBalanceIncrements); + computeProposers(fork, epochSeed, state.epochCtx.currentShuffling, state.epochCtx.effectiveBalanceIncrements); }, }); itBench({ id: `computeEpochShuffling - vc ${numValidators}`, fn: () => { - const {activeIndices} = state.epochCtx.nextShuffling; - computeEpochShuffling(state, activeIndices, activeIndices.length, nextEpoch); + const {nextActiveIndices} = state.epochCtx; + computeEpochShuffling(state, nextActiveIndices, nextEpoch); }, }); @@ -45,12 +45,7 @@ describe("epoch shufflings", () => { id: `getNextSyncCommittee - vc ${numValidators}`, fn: () => { const fork = state.config.getForkSeq(state.slot); - getNextSyncCommittee( - fork, - state, - state.epochCtx.nextShuffling.activeIndices, - state.epochCtx.effectiveBalanceIncrements - ); + getNextSyncCommittee(fork, state, state.epochCtx.nextActiveIndices, state.epochCtx.effectiveBalanceIncrements); }, }); }); diff --git a/packages/state-transition/test/perf/util/signingRoot.test.ts b/packages/state-transition/test/perf/util/signingRoot.test.ts index 96684c753364..1d308c2e3e43 100644 --- a/packages/state-transition/test/perf/util/signingRoot.test.ts +++ b/packages/state-transition/test/perf/util/signingRoot.test.ts @@ -15,7 +15,7 @@ import {computeSigningRoot} from "../../../src/util/signingRoot.js"; ✔ toHexString serialized data 727592.3 ops/s 1.374396 us/op - 6916 runs 10.0 s ✔ Buffer.toString(base64) 2570800 ops/s 388.9840 ns/op - 24628 runs 10.1 s */ -describe("computeSigningRoot", function () { +describe("computeSigningRoot", () => { setBenchOpts({ minMs: 10_000, }); diff --git a/packages/state-transition/test/unit/cachedBeaconState.test.ts b/packages/state-transition/test/unit/cachedBeaconState.test.ts index 77c5da7a5f4a..668f22e13a1e 100644 --- a/packages/state-transition/test/unit/cachedBeaconState.test.ts +++ b/packages/state-transition/test/unit/cachedBeaconState.test.ts @@ -1,15 +1,14 @@ import {fromHexString} from "@chainsafe/ssz"; import {describe, it, expect} from "vitest"; -import {Epoch, ssz, RootHex} from "@lodestar/types"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; +import {ssz} from "@lodestar/types"; import {toHexString} from "@lodestar/utils"; import {config as defaultConfig} from "@lodestar/config/default"; import {createBeaconConfig, createChainForkConfig} from "@lodestar/config"; import {createCachedBeaconStateTest} from "../utils/state.js"; -import {PubkeyIndexMap} from "../../src/cache/pubkeyCache.js"; import {createCachedBeaconState, loadCachedBeaconState} from "../../src/cache/stateCache.js"; import {interopPubkeysCached} from "../utils/interop.js"; import {modifyStateSameValidator, newStateWithValidators} from "../utils/capella.js"; -import {EpochShuffling, getShufflingDecisionBlock} from "../../src/util/epochShuffling.js"; describe("CachedBeaconState", () => { it("Clone and mutate", () => { @@ -53,7 +52,6 @@ describe("CachedBeaconState", () => { expect(state2.epochCtx.getValidatorIndex(pubkey2)).toBe(index2); }); - /* eslint-disable @typescript-eslint/naming-convention */ it("Clone and mutate cache post-Electra", () => { const stateView = ssz.electra.BeaconState.defaultViewDU(); const state1 = createCachedBeaconStateTest( @@ -84,7 +82,7 @@ describe("CachedBeaconState", () => { expect(state1.epochCtx.getValidatorIndex(pubkey1)).toBe(index1); expect(state2.epochCtx.getValidatorIndex(pubkey1)).toBe(index1); - expect(state1.epochCtx.getValidatorIndex(pubkey2)).toBe(undefined); + expect(state1.epochCtx.getValidatorIndex(pubkey2)).toBe(null); expect(state2.epochCtx.getValidatorIndex(pubkey2)).toBe(index2); }); @@ -189,42 +187,21 @@ describe("CachedBeaconState", () => { // confirm loadState() result const stateBytes = state.serialize(); - const newCachedState = loadCachedBeaconState(seedState, stateBytes, {skipSyncCommitteeCache: true}); + const newCachedState = loadCachedBeaconState(seedState, stateBytes, { + skipSyncCommitteeCache: true, + }); const newStateBytes = newCachedState.serialize(); expect(newStateBytes).toEqual(stateBytes); expect(newCachedState.hashTreeRoot()).toEqual(state.hashTreeRoot()); - const shufflingGetter = (shufflingEpoch: Epoch, dependentRoot: RootHex): EpochShuffling | null => { - if ( - shufflingEpoch === seedState.epochCtx.epoch - 1 && - dependentRoot === getShufflingDecisionBlock(seedState, shufflingEpoch) - ) { - return seedState.epochCtx.previousShuffling; - } - - if ( - shufflingEpoch === seedState.epochCtx.epoch && - dependentRoot === getShufflingDecisionBlock(seedState, shufflingEpoch) - ) { - return seedState.epochCtx.currentShuffling; - } - - if ( - shufflingEpoch === seedState.epochCtx.epoch + 1 && - dependentRoot === getShufflingDecisionBlock(seedState, shufflingEpoch) - ) { - return seedState.epochCtx.nextShuffling; - } - - return null; - }; const cachedState = createCachedBeaconState( state, { config, pubkey2index: new PubkeyIndexMap(), index2pubkey: [], + shufflingCache: seedState.epochCtx.shufflingCache, }, - {skipSyncCommitteeCache: true, shufflingGetter} + {skipSyncCommitteeCache: true} ); // validatorCountDelta < 0 is unrealistic and shuffling computation results in a different result if (validatorCountDelta >= 0) { diff --git a/packages/state-transition/test/unit/upgradeState.test.ts b/packages/state-transition/test/unit/upgradeState.test.ts index df9b052542f9..19a7d5c186f8 100644 --- a/packages/state-transition/test/unit/upgradeState.test.ts +++ b/packages/state-transition/test/unit/upgradeState.test.ts @@ -1,4 +1,5 @@ import {expect, describe, it} from "vitest"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {ssz} from "@lodestar/types"; import {ForkName} from "@lodestar/params"; import {createBeaconConfig, ChainForkConfig, createChainForkConfig} from "@lodestar/config"; @@ -7,7 +8,6 @@ import {config as chainConfig} from "@lodestar/config/default"; import {upgradeStateToDeneb} from "../../src/slot/upgradeStateToDeneb.js"; import {upgradeStateToElectra} from "../../src/slot/upgradeStateToElectra.js"; import {createCachedBeaconState} from "../../src/cache/stateCache.js"; -import {PubkeyIndexMap} from "../../src/cache/pubkeyCache.js"; describe("upgradeState", () => { it("upgradeStateToDeneb", () => { @@ -46,7 +46,6 @@ const ZERO_HASH = Buffer.alloc(32, 0); /** default config with ZERO_HASH as genesisValidatorsRoot */ const config = createBeaconConfig(chainConfig, ZERO_HASH); -/* eslint-disable @typescript-eslint/naming-convention */ function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig { switch (fork) { case ForkName.phase0: diff --git a/packages/state-transition/test/unit/util/aggregator.test.ts b/packages/state-transition/test/unit/util/aggregator.test.ts index 07fd3172926c..58c2b0afbf58 100644 --- a/packages/state-transition/test/unit/util/aggregator.test.ts +++ b/packages/state-transition/test/unit/util/aggregator.test.ts @@ -8,9 +8,7 @@ import { } from "@lodestar/params"; import {isAggregatorFromCommitteeLength, isSyncCommitteeAggregator} from "../../../src/util/aggregator.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - -describe("isAttestationAggregator", function () { +describe("isAttestationAggregator", () => { const committeeLength = 130; beforeAll(() => { @@ -21,7 +19,7 @@ describe("isAttestationAggregator", function () { }); }); - it("should be false", function () { + it("should be false", () => { const result = isAggregatorFromCommitteeLength( committeeLength, fromHexString( @@ -31,7 +29,7 @@ describe("isAttestationAggregator", function () { expect(result).toBe(false); }); - it("should be true", function () { + it("should be true", () => { const result = isAggregatorFromCommitteeLength( committeeLength, fromHexString( @@ -42,7 +40,7 @@ describe("isAttestationAggregator", function () { }); }); -describe("isSyncCommitteeAggregator", function () { +describe("isSyncCommitteeAggregator", () => { beforeAll(() => { expect({ SYNC_COMMITTEE_SIZE, @@ -55,7 +53,7 @@ describe("isSyncCommitteeAggregator", function () { }); }); - it("should be false", function () { + it("should be false", () => { const result = isSyncCommitteeAggregator( fromHexString( "0x8191d16330837620f0ed85d0d3d52af5b56f7cec12658fa391814251d4b32977eb2e6ca055367354fd63175f8d1d2d7b0678c3c482b738f96a0df40bd06450d99c301a659b8396c227ed781abb37a1604297922219374772ab36b46b84817036" @@ -65,7 +63,7 @@ describe("isSyncCommitteeAggregator", function () { }); // NOTE: Invalid sig, bruteforced last characters to get a true result - it("should be true", function () { + it("should be true", () => { const result = isSyncCommitteeAggregator( fromHexString( "0xa8f8bb92931234ca6d8a34530526bcd6a4cfa3bf33bd0470200dc8fa3ebdc3ba24bc8c6e994d58a0f884eb24336d746c01a29693ed0354c0862c2d5de5859e3f58747045182844d267ba232058f7df1867a406f63a1eb8afec0cf3f00a115142" diff --git a/packages/state-transition/test/unit/util/cachedBeaconState.test.ts b/packages/state-transition/test/unit/util/cachedBeaconState.test.ts index 654e0752adb8..c85a8c7a2ffd 100644 --- a/packages/state-transition/test/unit/util/cachedBeaconState.test.ts +++ b/packages/state-transition/test/unit/util/cachedBeaconState.test.ts @@ -1,8 +1,9 @@ import {describe, it} from "vitest"; +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {createBeaconConfig} from "@lodestar/config"; import {config} from "@lodestar/config/default"; import {ssz} from "@lodestar/types"; -import {createCachedBeaconState, PubkeyIndexMap} from "../../../src/index.js"; +import {createCachedBeaconState} from "../../../src/index.js"; describe("CachedBeaconState", () => { it("Create empty CachedBeaconState", () => { diff --git a/packages/state-transition/test/unit/util/deposit.test.ts b/packages/state-transition/test/unit/util/deposit.test.ts index 3cfa4abb3409..a682b4e993ed 100644 --- a/packages/state-transition/test/unit/util/deposit.test.ts +++ b/packages/state-transition/test/unit/util/deposit.test.ts @@ -29,7 +29,6 @@ describe("getEth1DepositCount", () => { const postElectraState = createCachedBeaconStateTest( stateView, createChainForkConfig({ - /* eslint-disable @typescript-eslint/naming-convention */ ALTAIR_FORK_EPOCH: 0, BELLATRIX_FORK_EPOCH: 0, CAPELLA_FORK_EPOCH: 0, @@ -63,7 +62,6 @@ describe("getEth1DepositCount", () => { const postElectraState = createCachedBeaconStateTest( stateView, createChainForkConfig({ - /* eslint-disable @typescript-eslint/naming-convention */ ALTAIR_FORK_EPOCH: 0, BELLATRIX_FORK_EPOCH: 0, CAPELLA_FORK_EPOCH: 0, diff --git a/packages/state-transition/test/unit/util/shuffle.test.ts b/packages/state-transition/test/unit/util/shuffle.test.ts deleted file mode 100644 index 9186968674ae..000000000000 --- a/packages/state-transition/test/unit/util/shuffle.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {expect, describe, it} from "vitest"; -import {unshuffleList} from "../../../src/index.js"; - -describe("util / shuffle", () => { - const testCases: { - id: string; - input: number[]; - res: number[]; - }[] = [ - // Values from `unshuffleList()` at commit https://github.com/ChainSafe/lodestar/commit/ec065635ca7da7f3788da018bd68c4900f0427d2 - { - id: "8 elements", - input: [0, 1, 2, 3, 4, 5, 6, 7], - res: [6, 3, 4, 0, 1, 5, 7, 2], - }, - { - id: "16 elements", - input: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - res: [8, 4, 11, 7, 10, 6, 0, 3, 15, 12, 5, 14, 1, 9, 13, 2], - }, - ]; - - const seed = new Uint8Array([42, 32]); - - it.each(testCases)("$id", ({input, res}) => { - unshuffleList(input, seed); - expect(input).toEqual(res); - }); -}); diff --git a/packages/state-transition/test/unit/util/shuffling.test.ts b/packages/state-transition/test/unit/util/shuffling.test.ts new file mode 100644 index 000000000000..f4039b472f5c --- /dev/null +++ b/packages/state-transition/test/unit/util/shuffling.test.ts @@ -0,0 +1,32 @@ +import {describe, it, expect} from "vitest"; +import {ssz} from "@lodestar/types"; +import {generateState} from "../../utils/state.js"; +import {computeEpochShuffling, computeEpochShufflingAsync} from "../../../src/util/epochShuffling.js"; +import {computeEpochAtSlot} from "../../../src/index.js"; + +describe("EpochShuffling", () => { + it("async and sync versions should be identical", async () => { + const numberOfValidators = 1000; + const activeIndices = Uint32Array.from(Array.from({length: numberOfValidators}, (_, i) => i)); + const state = generateState(); + state.slot = 12345; + state.validators = ssz.phase0.Validators.toViewDU( + Array.from({length: numberOfValidators}, () => ({ + activationEligibilityEpoch: 0, + activationEpoch: 0, + exitEpoch: Infinity, + effectiveBalance: 32, + pubkey: Buffer.alloc(48, 0xaa), + slashed: false, + withdrawableEpoch: Infinity, + withdrawalCredentials: Buffer.alloc(8, 0x01), + })) + ); + const epoch = computeEpochAtSlot(state.slot); + + const sync = computeEpochShuffling(state, activeIndices, epoch); + const async = await computeEpochShufflingAsync(state, activeIndices, epoch); + + expect(sync).toStrictEqual(async); + }); +}); diff --git a/packages/state-transition/test/unit/util/validator.test.ts b/packages/state-transition/test/unit/util/validator.test.ts index 65727126742d..203adf9d8ba3 100644 --- a/packages/state-transition/test/unit/util/validator.test.ts +++ b/packages/state-transition/test/unit/util/validator.test.ts @@ -49,7 +49,7 @@ describe("isActiveValidator", () => { describe("isSlashableValidator", () => { let validator: phase0.Validator; - beforeEach(function () { + beforeEach(() => { validator = generateValidator(); }); diff --git a/packages/state-transition/test/unit/util/weakSubjectivity.test.ts b/packages/state-transition/test/unit/util/weakSubjectivity.test.ts index 5f5c784e975a..ce9f02b5ed2f 100644 --- a/packages/state-transition/test/unit/util/weakSubjectivity.test.ts +++ b/packages/state-transition/test/unit/util/weakSubjectivity.test.ts @@ -4,7 +4,7 @@ import {computeWeakSubjectivityPeriodFromConstituents} from "../../../src/util/w import {getChurnLimit} from "../../../src/util/validator.js"; describe("weak subjectivity tests", () => { - describe("computeWeakSubjectivityPeriodFromConstituents", function () { + describe("computeWeakSubjectivityPeriodFromConstituents", () => { const balance28 = 28; const balance32 = 32; diff --git a/packages/state-transition/test/utils/beforeValue.ts b/packages/state-transition/test/utils/beforeValue.ts index f50520372e17..6eaa0846b155 100644 --- a/packages/state-transition/test/utils/beforeValue.ts +++ b/packages/state-transition/test/utils/beforeValue.ts @@ -14,23 +14,21 @@ export type LazyValue = {value: T}; export function beforeValue(fn: () => T | Promise, timeout?: number): LazyValue { let value: T = null as unknown as T; - beforeAll(async function () { + beforeAll(async () => { value = await fn(); }, timeout ?? 300_000); return new Proxy<{value: T}>( {value}, { - get: function (target, prop) { + get: (_target, prop) => { if (prop === "value") { if (value === null) { throw Error("beforeValue has not yet run the before() block"); - } else { - return value; } - } else { - return undefined; + return value; } + return undefined; }, } ); diff --git a/packages/state-transition/test/utils/beforeValueMocha.ts b/packages/state-transition/test/utils/beforeValueMocha.ts index 0d5f8f77d203..daac7c915d0c 100644 --- a/packages/state-transition/test/utils/beforeValueMocha.ts +++ b/packages/state-transition/test/utils/beforeValueMocha.ts @@ -20,16 +20,15 @@ export function beforeValue(fn: () => T | Promise, timeout?: number): Lazy return new Proxy<{value: T}>( {value}, { - get: function (target, prop) { + get: (_target, prop) => { if (prop === "value") { if (value === null) { throw Error("beforeValue has not yet run the before() block"); - } else { - return value; } - } else { - return undefined; + return value; } + + return undefined; }, } ); diff --git a/packages/state-transition/test/utils/rand.ts b/packages/state-transition/test/utils/rand.ts index 11a0efaffe10..d3dc3af278cc 100644 --- a/packages/state-transition/test/utils/rand.ts +++ b/packages/state-transition/test/utils/rand.ts @@ -3,8 +3,9 @@ * Source https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript */ export function mulberry32(a: number) { - return function () { - let t = (a += 0x6d2b79f5); + return () => { + a += 0x6d2b79f5; + let t = a; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; diff --git a/packages/state-transition/test/utils/specTestCases.ts b/packages/state-transition/test/utils/specTestCases.ts index 6c038d202d74..ed3776d868d0 100644 --- a/packages/state-transition/test/utils/specTestCases.ts +++ b/packages/state-transition/test/utils/specTestCases.ts @@ -3,7 +3,6 @@ import {fileURLToPath} from "node:url"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const SPEC_TEST_LOCATION = path.join(__dirname, "../../../../node_modules/@chainsafe/eth2-spec-tests"); diff --git a/packages/state-transition/test/utils/state.ts b/packages/state-transition/test/utils/state.ts index 29a1f98b5562..9a79faf74480 100644 --- a/packages/state-transition/test/utils/state.ts +++ b/packages/state-transition/test/utils/state.ts @@ -1,3 +1,4 @@ +import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {config as minimalConfig} from "@lodestar/config/default"; import { EPOCHS_PER_HISTORICAL_VECTOR, @@ -18,7 +19,6 @@ import { CachedBeaconStateAllForks, BeaconStateAllForks, createCachedBeaconState, - PubkeyIndexMap, } from "../../src/index.js"; import {BeaconStateCache} from "../../src/cache/stateCache.js"; import {EpochCacheOpts} from "../../src/cache/epochCache.js"; diff --git a/packages/state-transition/test/utils/testFileCache.ts b/packages/state-transition/test/utils/testFileCache.ts index b894674f54f6..5283a6f87fa1 100644 --- a/packages/state-transition/test/utils/testFileCache.ts +++ b/packages/state-transition/test/utils/testFileCache.ts @@ -41,23 +41,23 @@ export async function getNetworkCachedState( if (fs.existsSync(filepath)) { const stateSsz = fs.readFileSync(filepath); return createCachedBeaconStateTest(config.getForkTypes(slot).BeaconState.deserializeToViewDU(stateSsz), config); - } else { - const stateSsz = await tryEach([ - () => downloadTestFile(fileId), - () => { - const client = getClient( - {baseUrl: getInfuraBeaconUrl(network), globalInit: {timeoutMs: timeout ?? 300_000}}, - {config} - ); - return client.debug.getStateV2({stateId: slot}).then((r) => { - return r.ssz(); - }); - }, - ]); - - fs.writeFileSync(filepath, stateSsz); - return createCachedBeaconStateTest(config.getForkTypes(slot).BeaconState.deserializeToViewDU(stateSsz), config); } + + const stateSsz = await tryEach([ + () => downloadTestFile(fileId), + () => { + const client = getClient( + {baseUrl: getInfuraBeaconUrl(network), globalInit: {timeoutMs: timeout ?? 300_000}}, + {config} + ); + return client.debug.getStateV2({stateId: slot}).then((r) => { + return r.ssz(); + }); + }, + ]); + + fs.writeFileSync(filepath, stateSsz); + return createCachedBeaconStateTest(config.getForkTypes(slot).BeaconState.deserializeToViewDU(stateSsz), config); } /** @@ -76,27 +76,26 @@ export async function getNetworkCachedBlock( if (fs.existsSync(filepath)) { const blockSsz = fs.readFileSync(filepath); return config.getForkTypes(slot).SignedBeaconBlock.deserialize(blockSsz); - } else { - const blockSsz = await tryEach([ - () => downloadTestFile(fileId), - async () => { - const client = getClient( - {baseUrl: getInfuraBeaconUrl(network), globalInit: {timeoutMs: timeout ?? 300_000}}, - {config} - ); - - return (await client.beacon.getBlockV2({blockId: slot})).ssz(); - }, - ]); - - fs.writeFileSync(filepath, blockSsz); - return config.getForkTypes(slot).SignedBeaconBlock.deserialize(blockSsz); } + + const blockSsz = await tryEach([ + () => downloadTestFile(fileId), + async () => { + const client = getClient( + {baseUrl: getInfuraBeaconUrl(network), globalInit: {timeoutMs: timeout ?? 300_000}}, + {config} + ); + + return (await client.beacon.getBlockV2({blockId: slot})).ssz(); + }, + ]); + + fs.writeFileSync(filepath, blockSsz); + return config.getForkTypes(slot).SignedBeaconBlock.deserialize(blockSsz); } async function downloadTestFile(fileId: string): Promise { const fileUrl = `${TEST_FILES_BASE_URL}/${fileId}`; - // eslint-disable-next-line no-console console.log(`Downloading file ${fileUrl}`); const res = await got(fileUrl, {responseType: "buffer"}).catch((e: Error) => { diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 5bf61891a9d5..efc9c96ff5b1 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.22.0", + "version": "1.23.0", "description": "Test utilities reused across other packages", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -42,8 +42,8 @@ "build:watch": "yarn run build --watch", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/", + "lint:fix": "yarn run lint --write", "check-readme": "typescript-docs-verifier" }, "repository": { @@ -58,9 +58,9 @@ ], "dependencies": { "@chainsafe/bls-keystore": "^3.1.0", - "@chainsafe/blst": "^2.0.3", - "@lodestar/params": "^1.22.0", - "@lodestar/utils": "^1.22.0", + "@chainsafe/blst": "^2.2.0", + "@lodestar/params": "^1.23.0", + "@lodestar/utils": "^1.23.0", "axios": "^1.3.4", "testcontainers": "^10.2.1", "tmp": "^0.2.1", diff --git a/packages/test-utils/src/childProcess.ts b/packages/test-utils/src/childProcess.ts index cd6983cca467..d8b86b83ee48 100644 --- a/packages/test-utils/src/childProcess.ts +++ b/packages/test-utils/src/childProcess.ts @@ -86,7 +86,7 @@ export function isPidRunning(pid: number): boolean { // Signal 0 is a special signal that checks if the process exists process.kill(pid, 0); return true; - } catch { + } catch (_e) { return false; } } @@ -304,7 +304,7 @@ export async function spawnChildProcess( }); proc.removeAllListeners("exit"); resolve(proc); - } catch (error) { + } catch (_e) { reject( new Error( `Health check timeout. logPrefix=${logPrefix} pid=${proc.pid} healthTimeout=${prettyMsToTime(healthTimeoutMs ?? 0)}` diff --git a/packages/test-utils/src/cli.ts b/packages/test-utils/src/cli.ts index 8b4a84ec467a..a7b2a248bc7e 100644 --- a/packages/test-utils/src/cli.ts +++ b/packages/test-utils/src/cli.ts @@ -12,7 +12,7 @@ import { // We need to make it easy for the user to pass the args for the CLI // yargs treat `["--preset minimal"] as a single arg, so we need to split it ["--preset", "minimal"] function parseArgs(args: string[]): string[] { - return args.map((a) => a.split(" ")).flat(); + return args.flatMap((a) => a.split(" ")); } type CommandRunOptions = { @@ -28,7 +28,7 @@ export async function runCliCommand( opts: CommandRunOptions = {timeoutMs: 1000} ): Promise { return wrapTimeout( - // eslint-disable-next-line no-async-promise-executor + // biome-ignore lint/suspicious/noAsyncPromiseExecutor: We want to resolve with parser call back not on main promise new Promise(async (resolve, reject) => { try { await cli diff --git a/packages/test-utils/src/doubles.ts b/packages/test-utils/src/doubles.ts index c61c10ea6099..171c55824996 100644 --- a/packages/test-utils/src/doubles.ts +++ b/packages/test-utils/src/doubles.ts @@ -37,7 +37,6 @@ function wrapLogWriter(...writers: [writer: object, ...keys: string[]][]): { for (const key of keys) { originals[index][key] = writer[key as keyof typeof writer]; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error writer[key as keyof typeof writer] = function mockedWriter(data: string) { // Our fixtures does not include the new line character @@ -51,7 +50,6 @@ function wrapLogWriter(...writers: [writer: object, ...keys: string[]][]): { restore: () => { for (const [index, [writer, ...keys]] of writers.entries()) { for (const key of keys) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error writer[key as keyof typeof writer] = originals[index][key]; } diff --git a/packages/test-utils/src/externalSigner.ts b/packages/test-utils/src/externalSigner.ts index dbcb6bee1dc5..1e48e6af8d40 100644 --- a/packages/test-utils/src/externalSigner.ts +++ b/packages/test-utils/src/externalSigner.ts @@ -69,7 +69,6 @@ export async function startExternalSigner({ stream .on("data", (line) => process.stdout.write(line)) .on("err", (line) => process.stderr.write(line)) - // eslint-disable-next-line no-console .on("end", () => console.log("Stream closed")); return { diff --git a/packages/test-utils/src/http.ts b/packages/test-utils/src/http.ts index b4dd16390483..85b64c110cab 100644 --- a/packages/test-utils/src/http.ts +++ b/packages/test-utils/src/http.ts @@ -42,7 +42,6 @@ export async function matchReqSuccess(url: string, method: Method = "GET"): Prom * Wait for a given endpoint to return a given status code */ export async function waitForEndpoint(url: string, statusCode = 200): Promise { - // eslint-disable-next-line no-constant-condition while (true) { const status = await getReqStatus(url); diff --git a/packages/types/package.json b/packages/types/package.json index f3e034ec1b35..9201ef2f4e88 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.22.0", + "version": "1.23.0", "type": "module", "exports": { ".": { @@ -59,8 +59,8 @@ "build:release": "yarn clean && yarn build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test": "yarn test:unit", "test:constants:minimal": "LODESTAR_PRESET=minimal vitest --run --dir test/constants/", "test:constants:mainnet": "LODESTAR_PRESET=mainnet vitest --run --dir test/constants/", @@ -73,8 +73,8 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@chainsafe/ssz": "^0.17.1", - "@lodestar/params": "^1.22.0", + "@chainsafe/ssz": "^0.18.0", + "@lodestar/params": "^1.23.0", "ethereum-cryptography": "^2.0.0" }, "keywords": [ diff --git a/packages/types/src/electra/sszTypes.ts b/packages/types/src/electra/sszTypes.ts index 31844aac86cc..079af08352ec 100644 --- a/packages/types/src/electra/sszTypes.ts +++ b/packages/types/src/electra/sszTypes.ts @@ -8,7 +8,6 @@ import { } from "@chainsafe/ssz"; import { HISTORICAL_ROOTS_LIMIT, - BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH, EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD, @@ -18,7 +17,7 @@ import { MAX_ATTESTER_SLASHINGS_ELECTRA, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD, - PENDING_BALANCE_DEPOSITS_LIMIT, + PENDING_DEPOSITS_LIMIT, PENDING_PARTIAL_WITHDRAWALS_LIMIT, PENDING_CONSOLIDATIONS_LIMIT, FINALIZED_ROOT_DEPTH_ELECTRA, @@ -116,6 +115,8 @@ export const DepositRequest = new ContainerType( { pubkey: BLSPubkey, withdrawalCredentials: Bytes32, + // this is actually gwei uintbn64 type, but super unlikely to get a high amount here + // to warrant a bn type amount: UintNum64, signature: BLSSignature, index: DepositIndex, @@ -129,7 +130,7 @@ export const WithdrawalRequest = new ContainerType( { sourceAddress: ExecutionAddress, validatorPubkey: BLSPubkey, - amount: UintNum64, + amount: Gwei, }, {typeName: "WithdrawalRequest", jsonCase: "eth2"} ); @@ -147,25 +148,18 @@ export const ConsolidationRequests = new ListCompositeType( MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD ); -export const ExecutionPayload = new ContainerType( +export const ExecutionRequests = new ContainerType( { - ...denebSsz.ExecutionPayload.fields, - depositRequests: DepositRequests, // New in ELECTRA - withdrawalRequests: WithdrawalRequests, // New in ELECTRA - consolidationRequests: ConsolidationRequests, // New in ELECTRA + deposits: DepositRequests, + withdrawals: WithdrawalRequests, + consolidations: ConsolidationRequests, }, - {typeName: "ExecutionPayload", jsonCase: "eth2"} + {typeName: "ExecutionRequests", jsonCase: "eth2"} ); -export const ExecutionPayloadHeader = new ContainerType( - { - ...denebSsz.ExecutionPayloadHeader.fields, - depositRequestsRoot: Root, // New in ELECTRA - withdrawalRequestsRoot: Root, // New in ELECTRA - consolidationRequestsRoot: Root, // New in ELECTRA - }, - {typeName: "ExecutionPayloadHeader", jsonCase: "eth2"} -); +// Explicitly defining electra containers for consistency's sake +export const ExecutionPayloadHeader = denebSsz.ExecutionPayloadHeader; +export const ExecutionPayload = denebSsz.ExecutionPayload; // We have to preserve Fields ordering while changing the type of ExecutionPayload export const BeaconBlockBody = new ContainerType( @@ -179,9 +173,10 @@ export const BeaconBlockBody = new ContainerType( deposits: phase0Ssz.BeaconBlockBody.fields.deposits, voluntaryExits: phase0Ssz.BeaconBlockBody.fields.voluntaryExits, syncAggregate: altairSsz.BeaconBlockBody.fields.syncAggregate, - executionPayload: ExecutionPayload, // Modified in ELECTRA + executionPayload: ExecutionPayload, blsToExecutionChanges: capellaSsz.BeaconBlockBody.fields.blsToExecutionChanges, blobKzgCommitments: denebSsz.BeaconBlockBody.fields.blobKzgCommitments, + executionRequests: ExecutionRequests, // New in ELECTRA:EIP7251 }, {typeName: "BeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true} ); @@ -213,9 +208,10 @@ export const BlindedBeaconBlockBody = new ContainerType( deposits: phase0Ssz.BeaconBlockBody.fields.deposits, voluntaryExits: phase0Ssz.BeaconBlockBody.fields.voluntaryExits, syncAggregate: altairSsz.SyncAggregate, - executionPayloadHeader: ExecutionPayloadHeader, // Modified in ELECTRA + executionPayloadHeader: ExecutionPayloadHeader, blsToExecutionChanges: capellaSsz.BeaconBlockBody.fields.blsToExecutionChanges, blobKzgCommitments: denebSsz.BeaconBlockBody.fields.blobKzgCommitments, + executionRequests: ExecutionRequests, // New in ELECTRA }, {typeName: "BlindedBeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true} ); @@ -239,7 +235,8 @@ export const SignedBlindedBeaconBlock = new ContainerType( export const BuilderBid = new ContainerType( { header: ExecutionPayloadHeader, // Modified in ELECTRA - blindedBlobsBundle: denebSsz.BlobKzgCommitments, + blobKzgCommitments: denebSsz.BlobKzgCommitments, + executionRequests: ExecutionRequests, // New in ELECTRA value: UintBn256, pubkey: BLSPubkey, }, @@ -254,23 +251,20 @@ export const SignedBuilderBid = new ContainerType( {typeName: "SignedBuilderBid", jsonCase: "eth2"} ); -export const ExecutionPayloadAndBlobsBundle = new ContainerType( +export const PendingDeposit = new ContainerType( { - executionPayload: ExecutionPayload, // Modified in ELECTRA - blobsBundle: denebSsz.BlobsBundle, - }, - {typeName: "ExecutionPayloadAndBlobsBundle", jsonCase: "eth2"} -); - -export const PendingBalanceDeposit = new ContainerType( - { - index: ValidatorIndex, - amount: Gwei, + pubkey: BLSPubkey, + withdrawalCredentials: Bytes32, + // this is actually gwei uintbn64 type, but super unlikely to get a high amount here + // to warrant a bn type + amount: UintNum64, + signature: BLSSignature, + slot: Slot, }, - {typeName: "PendingBalanceDeposit", jsonCase: "eth2"} + {typeName: "PendingDeposit", jsonCase: "eth2"} ); -export const PendingBalanceDeposits = new ListCompositeType(PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT); +export const PendingDeposits = new ListCompositeType(PendingDeposit, PENDING_DEPOSITS_LIMIT); export const PendingPartialWithdrawal = new ContainerType( { @@ -326,7 +320,7 @@ export const BeaconState = new ContainerType( currentSyncCommittee: altairSsz.SyncCommittee, nextSyncCommittee: altairSsz.SyncCommittee, // Execution - latestExecutionPayloadHeader: ExecutionPayloadHeader, // Modified in ELECTRA + latestExecutionPayloadHeader: ExecutionPayloadHeader, // Withdrawals nextWithdrawalIndex: capellaSsz.BeaconState.fields.nextWithdrawalIndex, nextWithdrawalValidatorIndex: capellaSsz.BeaconState.fields.nextWithdrawalValidatorIndex, @@ -338,25 +332,16 @@ export const BeaconState = new ContainerType( earliestExitEpoch: Epoch, // New in ELECTRA:EIP7251 consolidationBalanceToConsume: Gwei, // New in ELECTRA:EIP7251 earliestConsolidationEpoch: Epoch, // New in ELECTRA:EIP7251 - pendingBalanceDeposits: PendingBalanceDeposits, // New in ELECTRA:EIP7251 + pendingDeposits: PendingDeposits, // New in ELECTRA:EIP7251 pendingPartialWithdrawals: new ListCompositeType(PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT), // New in ELECTRA:EIP7251 pendingConsolidations: new ListCompositeType(PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT), // New in ELECTRA:EIP7251 }, {typeName: "BeaconState", jsonCase: "eth2"} ); -export const LightClientHeader = new ContainerType( - { - beacon: phase0Ssz.BeaconBlockHeader, - execution: ExecutionPayloadHeader, // Modified in ELECTRA - executionBranch: new VectorCompositeType(Bytes32, EXECUTION_PAYLOAD_DEPTH), - }, - {typeName: "LightClientHeader", jsonCase: "eth2"} -); - export const LightClientBootstrap = new ContainerType( { - header: LightClientHeader, + header: denebSsz.LightClientHeader, currentSyncCommittee: altairSsz.SyncCommittee, currentSyncCommitteeBranch: new VectorCompositeType(Bytes32, NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA), }, @@ -365,11 +350,11 @@ export const LightClientBootstrap = new ContainerType( export const LightClientUpdate = new ContainerType( { - attestedHeader: LightClientHeader, + attestedHeader: denebSsz.LightClientHeader, nextSyncCommittee: altairSsz.SyncCommittee, - nextSyncCommitteeBranch: new VectorCompositeType(Bytes32, NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA), - finalizedHeader: LightClientHeader, - finalityBranch: new VectorCompositeType(Bytes32, FINALIZED_ROOT_DEPTH_ELECTRA), + nextSyncCommitteeBranch: new VectorCompositeType(Bytes32, NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA), // Modified in ELECTRA + finalizedHeader: denebSsz.LightClientHeader, + finalityBranch: new VectorCompositeType(Bytes32, FINALIZED_ROOT_DEPTH_ELECTRA), // Modified in ELECTRA syncAggregate: altairSsz.SyncAggregate, signatureSlot: Slot, }, @@ -378,9 +363,9 @@ export const LightClientUpdate = new ContainerType( export const LightClientFinalityUpdate = new ContainerType( { - attestedHeader: LightClientHeader, - finalizedHeader: LightClientHeader, - finalityBranch: new VectorCompositeType(Bytes32, FINALIZED_ROOT_DEPTH_ELECTRA), + attestedHeader: denebSsz.LightClientHeader, + finalizedHeader: denebSsz.LightClientHeader, + finalityBranch: new VectorCompositeType(Bytes32, FINALIZED_ROOT_DEPTH_ELECTRA), // Modified in ELECTRA syncAggregate: altairSsz.SyncAggregate, signatureSlot: Slot, }, @@ -389,7 +374,7 @@ export const LightClientFinalityUpdate = new ContainerType( export const LightClientOptimisticUpdate = new ContainerType( { - attestedHeader: LightClientHeader, + attestedHeader: denebSsz.LightClientHeader, syncAggregate: altairSsz.SyncAggregate, signatureSlot: Slot, }, diff --git a/packages/types/src/electra/types.ts b/packages/types/src/electra/types.ts index 9a81aec43b53..691de409ed91 100644 --- a/packages/types/src/electra/types.ts +++ b/packages/types/src/electra/types.ts @@ -20,8 +20,7 @@ export type ConsolidationRequests = ValueOf; export type ExecutionPayload = ValueOf; export type ExecutionPayloadHeader = ValueOf; - -export type ExecutionPayloadAndBlobsBundle = ValueOf; +export type ExecutionRequests = ValueOf; export type BeaconBlockBody = ValueOf; export type BeaconBlock = ValueOf; @@ -33,20 +32,17 @@ export type BlindedBeaconBlockBody = ValueOf; export type BlindedBeaconBlock = ValueOf; export type SignedBlindedBeaconBlock = ValueOf; -export type FullOrBlindedExecutionPayload = ExecutionPayload | ExecutionPayloadHeader; - export type BuilderBid = ValueOf; export type SignedBuilderBid = ValueOf; export type SSEPayloadAttributes = ValueOf; -export type LightClientHeader = ValueOf; export type LightClientBootstrap = ValueOf; export type LightClientUpdate = ValueOf; export type LightClientFinalityUpdate = ValueOf; export type LightClientOptimisticUpdate = ValueOf; export type LightClientStore = ValueOf; -export type PendingBalanceDeposit = ValueOf; +export type PendingDeposit = ValueOf; export type PendingPartialWithdrawal = ValueOf; export type PendingConsolidation = ValueOf; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index e0745834c7d1..dc9139dc967e 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -9,3 +9,4 @@ export * from "./utils/typeguards.js"; export {StringType, stringType} from "./utils/stringType.js"; // Container utils export * from "./utils/container.js"; +export * from "./utils/validatorStatus.js"; diff --git a/packages/types/src/phase0/validator.ts b/packages/types/src/phase0/validator.ts index 3c2f72aac509..aaff2deaeb27 100644 --- a/packages/types/src/phase0/validator.ts +++ b/packages/types/src/phase0/validator.ts @@ -1,6 +1,7 @@ import {ByteViews, ContainerNodeStructType, ValueOfFields} from "@chainsafe/ssz"; import * as primitiveSsz from "../primitive/sszTypes.js"; +// biome-ignore lint/suspicious/noShadowRestrictedNames: We explicitly want `Boolean` name to be imported const {Boolean, Bytes32, UintNum64, BLSPubkey, EpochInf} = primitiveSsz; // this is to work with uint32, see https://github.com/ChainSafe/ssz/blob/ssz-v0.15.1/packages/ssz/src/type/uint.ts @@ -33,6 +34,7 @@ export class ValidatorNodeStructType extends ContainerNodeStructType type UnionSSZForksTypeOf> = CompositeType< ValueOf, CompositeView, @@ -41,11 +42,9 @@ type SSZTypesByFork = { 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]>; diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 1071eed79a10..b1919c2f0842 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -1,4 +1,12 @@ -import {ForkAll, ForkBlobs, ForkExecution, ForkLightClient, ForkName, ForkPreBlobs} from "@lodestar/params"; +import { + ForkAll, + ForkBlobs, + ForkExecution, + ForkLightClient, + ForkName, + ForkPostElectra, + 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"; @@ -24,6 +32,18 @@ export enum ProducedBlockSource { engine = "engine", } +export type WithBytes = { + data: T; + /** SSZ serialized `data` bytes */ + bytes: Uint8Array; +}; + +export type WithOptionalBytes = { + data: T; + /** SSZ serialized `data` bytes */ + bytes?: Uint8Array | null; +}; + export type SlotRootHex = {slot: Slot; root: RootHex}; export type SlotOptionalRoot = {slot: Slot; root?: RootHex}; @@ -172,7 +192,7 @@ type TypesByFork = { BeaconState: electra.BeaconState; SignedBeaconBlock: electra.SignedBeaconBlock; Metadata: altair.Metadata; - LightClientHeader: electra.LightClientHeader; + LightClientHeader: deneb.LightClientHeader; LightClientBootstrap: electra.LightClientBootstrap; LightClientUpdate: electra.LightClientUpdate; LightClientFinalityUpdate: electra.LightClientFinalityUpdate; @@ -181,8 +201,8 @@ type TypesByFork = { BlindedBeaconBlock: electra.BlindedBeaconBlock; BlindedBeaconBlockBody: electra.BlindedBeaconBlockBody; SignedBlindedBeaconBlock: electra.SignedBlindedBeaconBlock; - ExecutionPayload: electra.ExecutionPayload; - ExecutionPayloadHeader: electra.ExecutionPayloadHeader; + ExecutionPayload: deneb.ExecutionPayload; + ExecutionPayloadHeader: deneb.ExecutionPayloadHeader; BuilderBid: electra.BuilderBid; SignedBuilderBid: electra.SignedBuilderBid; SSEPayloadAttributes: electra.SSEPayloadAttributes; @@ -199,6 +219,7 @@ type TypesByFork = { AttesterSlashing: electra.AttesterSlashing; AggregateAndProof: electra.AggregateAndProof; SignedAggregateAndProof: electra.SignedAggregateAndProof; + ExecutionRequests: electra.ExecutionRequests; }; }; @@ -233,6 +254,7 @@ export type SignedBeaconBlockOrContents = TypesByFork[F]["ExecutionPayload"]; export type ExecutionPayloadHeader = TypesByFork[F]["ExecutionPayloadHeader"]; +export type ExecutionRequests = TypesByFork[F]["ExecutionRequests"]; export type BlobsBundle = TypesByFork[F]["BlobsBundle"]; export type Contents = TypesByFork[F]["Contents"]; diff --git a/packages/types/src/utils/typeguards.ts b/packages/types/src/utils/typeguards.ts index 72910645f6e1..a892c3a0c9c0 100644 --- a/packages/types/src/utils/typeguards.ts +++ b/packages/types/src/utils/typeguards.ts @@ -1,4 +1,4 @@ -import {ForkBlobs, ForkExecution, ForkPostElectra} from "@lodestar/params"; +import {FINALIZED_ROOT_DEPTH_ELECTRA, ForkBlobs, ForkExecution, ForkPostElectra} from "@lodestar/params"; import { BlockContents, SignedBeaconBlock, @@ -14,6 +14,7 @@ import { SignedBlockContents, BeaconBlock, Attestation, + LightClientUpdate, } from "../types.js"; export function isExecutionPayload( @@ -71,3 +72,11 @@ export function isSignedBlockContents( export function isElectraAttestation(attestation: Attestation): attestation is Attestation { return (attestation as Attestation).committeeBits !== undefined; } + +export function isElectraLightClientUpdate(update: LightClientUpdate): update is LightClientUpdate { + const updatePostElectra = update as LightClientUpdate; + return ( + updatePostElectra.finalityBranch !== undefined && + updatePostElectra.finalityBranch.length === FINALIZED_ROOT_DEPTH_ELECTRA + ); +} diff --git a/packages/types/src/utils/validatorStatus.ts b/packages/types/src/utils/validatorStatus.ts new file mode 100644 index 000000000000..9e72c39d9df2 --- /dev/null +++ b/packages/types/src/utils/validatorStatus.ts @@ -0,0 +1,52 @@ +import {FAR_FUTURE_EPOCH} from "@lodestar/params"; +import {Epoch, phase0} from "../types.js"; + +/** + * [Validator status specification](https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ) + */ +export type ValidatorStatus = + | "pending_initialized" + | "pending_queued" + | "active_ongoing" + | "active_exiting" + | "active_slashed" + | "exited_unslashed" + | "exited_slashed" + | "withdrawal_possible" + | "withdrawal_done"; + +/** + * Get the status of the validator + * based on conditions outlined in https://hackmd.io/ofFJ5gOmQpu1jjHilHbdQQ + */ +export function getValidatorStatus(validator: phase0.Validator, currentEpoch: Epoch): ValidatorStatus { + // pending + if (validator.activationEpoch > currentEpoch) { + if (validator.activationEligibilityEpoch === FAR_FUTURE_EPOCH) { + return "pending_initialized"; + } + + if (validator.activationEligibilityEpoch < FAR_FUTURE_EPOCH) { + return "pending_queued"; + } + } + // active + if (validator.activationEpoch <= currentEpoch && currentEpoch < validator.exitEpoch) { + if (validator.exitEpoch === FAR_FUTURE_EPOCH) { + return "active_ongoing"; + } + + if (validator.exitEpoch < FAR_FUTURE_EPOCH) { + return validator.slashed ? "active_slashed" : "active_exiting"; + } + } + // exited + if (validator.exitEpoch <= currentEpoch && currentEpoch < validator.withdrawableEpoch) { + return validator.slashed ? "exited_slashed" : "exited_unslashed"; + } + // withdrawal + if (validator.withdrawableEpoch <= currentEpoch) { + return validator.effectiveBalance !== 0 ? "withdrawal_possible" : "withdrawal_done"; + } + throw new Error("ValidatorStatus unknown"); +} diff --git a/packages/types/test/unit/blinded.test.ts b/packages/types/test/unit/blinded.test.ts index 3a4b346d29bf..3d087b610b2d 100644 --- a/packages/types/test/unit/blinded.test.ts +++ b/packages/types/test/unit/blinded.test.ts @@ -2,7 +2,7 @@ import {describe, it, expect} from "vitest"; import {ForkName, isForkExecution} from "@lodestar/params"; import {ssz} from "../../src/index.js"; -describe("blinded data structures", function () { +describe("blinded data structures", () => { it("should have the same number of fields as non-blinded", () => { const blindedTypes = [ {a: "BlindedBeaconBlockBody" as const, b: "BeaconBlockBody" as const}, diff --git a/packages/types/test/unit/phase0/sszTypes.test.ts b/packages/types/test/unit/phase0/sszTypes.test.ts index 0cda0fc888f6..4bdb2031e5ea 100644 --- a/packages/types/test/unit/phase0/sszTypes.test.ts +++ b/packages/types/test/unit/phase0/sszTypes.test.ts @@ -5,7 +5,7 @@ import {ValidatorType} from "../../../src/phase0/validator.js"; const ValidatorContainer = new ContainerType(ValidatorType, {typeName: "Validator", jsonCase: "eth2"}); -describe("Validator ssz types", function () { +describe("Validator ssz types", () => { it("should serialize to the same value", () => { const seedValidator = { activationEligibilityEpoch: 10, diff --git a/packages/types/test/unit/ssz.test.ts b/packages/types/test/unit/ssz.test.ts index b5c972a8f471..41e4e0bbd23b 100644 --- a/packages/types/test/unit/ssz.test.ts +++ b/packages/types/test/unit/ssz.test.ts @@ -1,7 +1,7 @@ import {describe, it, expect} from "vitest"; import {ssz} from "../../src/index.js"; -describe("size", function () { +describe("size", () => { it("should calculate correct minSize and maxSize", () => { const minSize = ssz.phase0.BeaconState.minSize; const maxSize = ssz.phase0.BeaconState.maxSize; @@ -11,8 +11,8 @@ describe("size", function () { }); }); -describe("container serialization/deserialization field casing(s)", function () { - it("AttesterSlashing", function () { +describe("container serialization/deserialization field casing(s)", () => { + it("AttesterSlashing", () => { const test = { attestation1: ssz.phase0.IndexedAttestation.defaultValue(), attestation2: ssz.phase0.IndexedAttestation.defaultValue(), @@ -27,7 +27,7 @@ describe("container serialization/deserialization field casing(s)", function () expect(back).toEqual(json); }); - it("ProposerSlashing", function () { + it("ProposerSlashing", () => { const test = { signedHeader1: ssz.phase0.SignedBeaconBlockHeader.defaultValue(), signedHeader2: ssz.phase0.SignedBeaconBlockHeader.defaultValue(), diff --git a/packages/types/test/unit/validatorStatus.test.ts b/packages/types/test/unit/validatorStatus.test.ts new file mode 100644 index 000000000000..b5bd34004bdc --- /dev/null +++ b/packages/types/test/unit/validatorStatus.test.ts @@ -0,0 +1,100 @@ +import {describe, it, expect} from "vitest"; +import {getValidatorStatus} from "../../src/utils/validatorStatus.js"; +import {phase0} from "../../src/types.js"; + +describe("getValidatorStatus", () => { + it("should return PENDING_INITIALIZED", () => { + const validator = { + activationEpoch: 1, + activationEligibilityEpoch: Infinity, + } as phase0.Validator; + const currentEpoch = 0; + const status = getValidatorStatus(validator, currentEpoch); + expect(status).toBe("pending_initialized"); + }); + it("should return PENDING_QUEUED", () => { + const validator = { + activationEpoch: 1, + activationEligibilityEpoch: 101010101101010, + } as phase0.Validator; + const currentEpoch = 0; + const status = getValidatorStatus(validator, currentEpoch); + expect(status).toBe("pending_queued"); + }); + it("should return ACTIVE_ONGOING", () => { + const validator = { + activationEpoch: 1, + exitEpoch: Infinity, + } as phase0.Validator; + const currentEpoch = 1; + const status = getValidatorStatus(validator, currentEpoch); + expect(status).toBe("active_ongoing"); + }); + it("should return ACTIVE_SLASHED", () => { + const validator = { + activationEpoch: 1, + exitEpoch: 101010101101010, + slashed: true, + } as phase0.Validator; + const currentEpoch = 1; + const status = getValidatorStatus(validator, currentEpoch); + expect(status).toBe("active_slashed"); + }); + it("should return ACTIVE_EXITING", () => { + const validator = { + activationEpoch: 1, + exitEpoch: 101010101101010, + slashed: false, + } as phase0.Validator; + const currentEpoch = 1; + const status = getValidatorStatus(validator, currentEpoch); + expect(status).toBe("active_exiting"); + }); + it("should return EXITED_SLASHED", () => { + const validator = { + exitEpoch: 1, + withdrawableEpoch: 3, + slashed: true, + } as phase0.Validator; + const currentEpoch = 2; + const status = getValidatorStatus(validator, currentEpoch); + expect(status).toBe("exited_slashed"); + }); + it("should return EXITED_UNSLASHED", () => { + const validator = { + exitEpoch: 1, + withdrawableEpoch: 3, + slashed: false, + } as phase0.Validator; + const currentEpoch = 2; + const status = getValidatorStatus(validator, currentEpoch); + expect(status).toBe("exited_unslashed"); + }); + it("should return WITHDRAWAL_POSSIBLE", () => { + const validator = { + withdrawableEpoch: 1, + effectiveBalance: 32, + } as phase0.Validator; + const currentEpoch = 1; + const status = getValidatorStatus(validator, currentEpoch); + expect(status).toBe("withdrawal_possible"); + }); + it("should return WITHDRAWAL_DONE", () => { + const validator = { + withdrawableEpoch: 1, + effectiveBalance: 0, + } as phase0.Validator; + const currentEpoch = 1; + const status = getValidatorStatus(validator, currentEpoch); + expect(status).toBe("withdrawal_done"); + }); + it("should error", () => { + const validator = {} as phase0.Validator; + const currentEpoch = 0; + try { + getValidatorStatus(validator, currentEpoch); + } catch (error) { + expect(error).toHaveProperty("message", "ValidatorStatus unknown"); + } + }); +}); diff --git a/packages/utils/package.json b/packages/utils/package.json index 0723f5f1815c..9b6f5e797e68 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.22.0", + "version": "1.23.0", "type": "module", "exports": "./lib/index.js", "files": [ @@ -28,8 +28,8 @@ "build:release": "yarn clean && yarn build", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc && vitest --run --typecheck --dir test/types/", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test:unit": "vitest --run --dir test/unit", "test:browsers": "yarn test:browsers:chrome && yarn test:browsers:firefox && yarn test:browsers:electron", "test:browsers:chrome": "vitest --run --browser chrome --config ./vitest.browser.config.ts --dir test/unit", diff --git a/packages/utils/src/assert.ts b/packages/utils/src/assert.ts index 91612b0e6407..7b6af1f00f97 100644 --- a/packages/utils/src/assert.ts +++ b/packages/utils/src/assert.ts @@ -16,7 +16,6 @@ export const assert = { */ equal(actual: T, expected: T, message?: string): void { if (!(actual === expected)) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new AssertionError(`${message || "Expected values to be equal"}: ${actual} === ${expected}`); } }, diff --git a/packages/utils/src/bytes.ts b/packages/utils/src/bytes.ts index 95bb62ebd548..c290232ce8db 100644 --- a/packages/utils/src/bytes.ts +++ b/packages/utils/src/bytes.ts @@ -34,7 +34,8 @@ export function bytesToInt(value: Uint8Array, endianness: Endianness = "le"): nu export function bigIntToBytes(value: bigint, length: number, endianness: Endianness = "le"): Buffer { if (endianness === "le") { return toBufferLE(value, length); - } else if (endianness === "be") { + } + if (endianness === "be") { return toBufferBE(value, length); } throw new Error("endianness must be either 'le' or 'be'"); @@ -43,7 +44,8 @@ export function bigIntToBytes(value: bigint, length: number, endianness: Endiann export function bytesToBigInt(value: Uint8Array, endianness: Endianness = "le"): bigint { if (endianness === "le") { return toBigIntLE(value as Buffer); - } else if (endianness === "be") { + } + if (endianness === "be") { return toBigIntBE(value as Buffer); } throw new Error("endianness must be either 'le' or 'be'"); diff --git a/packages/utils/src/bytes/nodejs.ts b/packages/utils/src/bytes/nodejs.ts index 636f49bd8e76..efa7a585835f 100644 --- a/packages/utils/src/bytes/nodejs.ts +++ b/packages/utils/src/bytes/nodejs.ts @@ -1,11 +1,11 @@ export function toHex(buffer: Uint8Array | Parameters[0]): string { if (Buffer.isBuffer(buffer)) { return "0x" + buffer.toString("hex"); - } else if (buffer instanceof Uint8Array) { + } + if (buffer instanceof Uint8Array) { return "0x" + Buffer.from(buffer.buffer, buffer.byteOffset, buffer.length).toString("hex"); - } else { - return "0x" + Buffer.from(buffer).toString("hex"); } + return "0x" + Buffer.from(buffer).toString("hex"); } // Shared buffer to convert root to hex @@ -44,6 +44,18 @@ export function toPubkeyHex(pubkey: Uint8Array): string { } export function fromHex(hex: string): Uint8Array { - const b = Buffer.from(hex.replace("0x", ""), "hex"); + if (typeof hex !== "string") { + throw new Error(`hex argument type ${typeof hex} must be of type string`); + } + + if (hex.startsWith("0x")) { + hex = hex.slice(2); + } + + if (hex.length % 2 !== 0) { + throw new Error(`hex string length ${hex.length} must be multiple of 2`); + } + + const b = Buffer.from(hex, "hex"); return new Uint8Array(b.buffer, b.byteOffset, b.length); } diff --git a/packages/utils/src/command.ts b/packages/utils/src/command.ts index 89929a6c41ef..2e62ba5a9648 100644 --- a/packages/utils/src/command.ts +++ b/packages/utils/src/command.ts @@ -6,7 +6,7 @@ export interface CliExample { description?: string; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export interface CliOptionDefinition extends Options { example?: Omit; // Ensure `type` property matches type of `T` @@ -28,7 +28,7 @@ export type CliCommandOptions = Required<{ CliOptionDefinition & (Required> | {demandOption: true}); }>; -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export interface CliCommand, ParentArgs = Record, R = any> { command: string; describe: string; @@ -41,7 +41,7 @@ export interface CliCommand, ParentArgs = Record< options?: CliCommandOptions; // 1st arg: any = free own sub command options // 2nd arg: subcommand parent options is = to this command options + parent options - // eslint-disable-next-line @typescript-eslint/no-explicit-any + // biome-ignore lint/suspicious/noExplicitAny: subcommands?: CliCommand[]; handler?: (args: OwnArgs & ParentArgs) => Promise; } @@ -51,7 +51,8 @@ export interface CliCommand, ParentArgs = Record< * @param yargs * @param cliCommand */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any + +// biome-ignore lint/suspicious/noExplicitAny: export function registerCommandToYargs(yargs: Argv, cliCommand: CliCommand): void { yargs.command({ command: cliCommand.command, diff --git a/packages/utils/src/diff.ts b/packages/utils/src/diff.ts index 204989016b46..f7357cde19fa 100644 --- a/packages/utils/src/diff.ts +++ b/packages/utils/src/diff.ts @@ -203,17 +203,19 @@ export function getDiffs(val1: Diffable, val2: Diffable, objectPath: string): Di */ export function diff(val1: unknown, val2: unknown, outputValues = false, filename?: string): void { if (!isDiffable(val1)) { + // biome-ignore lint/suspicious/noConsoleLog: console.log("val1 is not Diffable"); return; } if (!isDiffable(val2)) { + // biome-ignore lint/suspicious/noConsoleLog: console.log("val2 is not Diffable"); return; } const diffs = getDiffs(val1, val2, ""); let output = ""; if (diffs.length) { - diffs.forEach((diff) => { + for (const diff of diffs) { let diffOutput = `value${diff.objectPath}`; if (diff.errorMessage) { diffOutput += `\n ${diff.errorMessage}`; @@ -222,10 +224,11 @@ export function diff(val1: unknown, val2: unknown, outputValues = false, filenam diffOutput += `\n - ${diff.val1.toString()}\n - ${diff.val2.toString()}\n`; } output += `${diffOutput}\n`; - }); + } if (filename) { fs.writeFileSync(filename, output); } else { + // biome-ignore lint/suspicious/noConsoleLog: console.log(output); } } diff --git a/packages/utils/src/err.ts b/packages/utils/src/err.ts index 81f6f92c0044..7c27a0a067a3 100644 --- a/packages/utils/src/err.ts +++ b/packages/utils/src/err.ts @@ -4,7 +4,6 @@ export type Err = {[symErr]: true; error: T}; export type Result = T | Err; -// eslint-disable-next-line @typescript-eslint/naming-convention export function Err(error: T): Err { return {[symErr]: true, error}; } diff --git a/packages/utils/src/format.ts b/packages/utils/src/format.ts index 8bd8a40273f1..b36412072720 100644 --- a/packages/utils/src/format.ts +++ b/packages/utils/src/format.ts @@ -1,4 +1,4 @@ -import {toHexString} from "./bytes.js"; +import {toRootHex} from "./bytes/index.js"; import {ETH_TO_WEI} from "./ethConversion.js"; /** @@ -6,7 +6,7 @@ import {ETH_TO_WEI} from "./ethConversion.js"; * 4 bytes can represent 4294967296 values, so the chance of collision is low */ export function prettyBytes(root: Uint8Array | string): string { - const str = typeof root === "string" ? root : toHexString(root); + const str = typeof root === "string" ? root : toRootHex(root); return `${str.slice(0, 6)}…${str.slice(-4)}`; } @@ -15,7 +15,7 @@ export function prettyBytes(root: Uint8Array | string): string { * Paired with block numbers or slots, it can still act as a decent identify-able format */ export function prettyBytesShort(root: Uint8Array | string): string { - const str = typeof root === "string" ? root : toHexString(root); + const str = typeof root === "string" ? root : toRootHex(root); return `${str.slice(0, 6)}…`; } @@ -25,7 +25,7 @@ export function prettyBytesShort(root: Uint8Array | string): string { * values on explorers like beaconcha.in while improving readability of logs */ export function truncBytes(root: Uint8Array | string): string { - const str = typeof root === "string" ? root : toHexString(root); + const str = typeof root === "string" ? root : toRootHex(root); return str.slice(0, 14); } @@ -44,11 +44,18 @@ export function formatBigDecimal(numerator: bigint, denominator: bigint, maxDeci // display upto 5 decimal places const MAX_DECIMAL_FACTOR = BigInt("100000"); +/** + * Format wei as ETH, with up to 5 decimals + */ +export function formatWeiToEth(wei: bigint): string { + return formatBigDecimal(wei, ETH_TO_WEI, MAX_DECIMAL_FACTOR); +} + /** * Format wei as ETH, with up to 5 decimals and append ' ETH' */ export function prettyWeiToEth(wei: bigint): string { - return `${formatBigDecimal(wei, ETH_TO_WEI, MAX_DECIMAL_FACTOR)} ETH`; + return `${formatWeiToEth(wei)} ETH`; } /** diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts index 925a357f4b98..e299e730a81a 100644 --- a/packages/utils/src/logger.ts +++ b/packages/utils/src/logger.ts @@ -12,7 +12,6 @@ export enum LogLevel { trace = "trace", } -// eslint-disable-next-line @typescript-eslint/naming-convention export const LogLevels = Object.values(LogLevel); export type LogHandler = (message: string, context?: LogData, error?: Error) => void; diff --git a/packages/utils/src/objects.ts b/packages/utils/src/objects.ts index ad09d36b0ecc..602e758e360c 100644 --- a/packages/utils/src/objects.ts +++ b/packages/utils/src/objects.ts @@ -18,7 +18,7 @@ export function toExpectedCase( customCasingMap?: Record ): string { if (expectedCase === "notransform") return value; - if (customCasingMap && customCasingMap[value]) return customCasingMap[value]; + if (customCasingMap?.[value]) return customCasingMap[value]; switch (expectedCase) { case "param": return Case.kebab(value); @@ -45,7 +45,7 @@ export function isPlainObject(o: unknown): o is object { if (isObjectObject(prot) === false) return false; // If constructor does not have an Object-specific method - if (prot.hasOwnProperty("isPrototypeOf") === false) { + if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) { return false; } @@ -63,7 +63,7 @@ export function isEmptyObject(value: unknown): boolean { * * Inspired on lodash.mapValues, see https://lodash.com/docs/4.17.15#mapValues */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any +// biome-ignore lint/suspicious/noExplicitAny: export function mapValues( obj: T, iteratee: (value: T[keyof T], key: keyof T) => R @@ -91,7 +91,7 @@ export function objectToExpectedCase | Record< const newObj: Record = {}; for (const name of Object.getOwnPropertyNames(obj)) { const newName = toExpectedCase(name, expectedCase); - if (newName !== name && obj.hasOwnProperty(newName)) { + if (newName !== name && Object.prototype.hasOwnProperty.call(obj, newName)) { throw new Error(`object already has a ${newName} property`); } diff --git a/packages/utils/src/promise.ts b/packages/utils/src/promise.ts index 2be7467bfff4..894444d46937 100644 --- a/packages/utils/src/promise.ts +++ b/packages/utils/src/promise.ts @@ -109,7 +109,6 @@ export async function resolveOrRacePromises wrapPromise(p)) as ReturnPromiseWithTuple; // We intentionally want an array of promises here - // eslint-disable-next-line @typescript-eslint/no-floating-promises promises = (promiseResults as PromiseResult[]).map((p) => p.promise) as unknown as T; try { diff --git a/packages/utils/src/retry.ts b/packages/utils/src/retry.ts index 6c5e63deca42..bc759d62e9b2 100644 --- a/packages/utils/src/retry.ts +++ b/packages/utils/src/retry.ts @@ -60,9 +60,13 @@ export async function retry(fn: (attempt: number) => A | Promise, opts?: R if (i === maxAttempts) { // Reached maximum number of attempts, there's no need to check if we should retry break; - } else if (shouldRetry && !shouldRetry(lastError)) { + } + + if (shouldRetry && !shouldRetry(lastError)) { break; - } else if (opts?.retryDelay !== undefined) { + } + + if (opts?.retryDelay !== undefined) { await sleep(opts?.retryDelay, opts?.signal); } } diff --git a/packages/utils/src/sleep.ts b/packages/utils/src/sleep.ts index c31a9daffd32..f2ff74255f48 100644 --- a/packages/utils/src/sleep.ts +++ b/packages/utils/src/sleep.ts @@ -10,7 +10,7 @@ export async function sleep(ms: number, signal?: AbortSignal): Promise { } return new Promise((resolve, reject) => { - if (signal && signal.aborted) return reject(new ErrorAborted()); + if (signal?.aborted) return reject(new ErrorAborted()); let onDone: () => void = () => {}; diff --git a/packages/utils/src/url.ts b/packages/utils/src/url.ts index f00b70edbde2..d4eba3bd67cf 100644 --- a/packages/utils/src/url.ts +++ b/packages/utils/src/url.ts @@ -1,5 +1,5 @@ export function isValidHttpUrl(urlStr: string): boolean { - let url; + let url: URL; try { url = new URL(urlStr); diff --git a/packages/utils/src/yaml/int.ts b/packages/utils/src/yaml/int.ts index 64a2c283dd49..264d1297f888 100644 --- a/packages/utils/src/yaml/int.ts +++ b/packages/utils/src/yaml/int.ts @@ -27,7 +27,7 @@ function resolveYamlInteger(data: string): boolean { if (data === null) return false; const max = data.length; - let ch, + let ch: string, index = 0, hasDigits = false; @@ -111,7 +111,7 @@ function resolveYamlInteger(data: string): boolean { function constructYamlInteger(data: string): bigint { let value: string | bigint = data, sign = 1, - ch, + ch: string, base: number | bigint; const digits: number[] = []; @@ -162,25 +162,20 @@ export const intType = new Type("tag:yaml.org,2002:int", { construct: constructYamlInteger, predicate: isInteger, instanceOf: BigInt, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore represent: { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore binary: function binary(obj: number) { return obj >= 0 ? "0b" + obj.toString(2) : "-0b" + obj.toString(2).slice(1); }, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore octal: function octal(obj: number) { return obj >= 0 ? "0" + obj.toString(8) : "-0" + obj.toString(8).slice(1); }, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore decimal: function decimal(obj: number) { return obj.toString(10); }, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore hexadecimal: function hexadecimal(obj: number) { return obj >= 0 ? "0x" + obj.toString(16).toUpperCase() : "-0x" + obj.toString(16).toUpperCase().slice(1); diff --git a/packages/utils/src/yaml/schema.ts b/packages/utils/src/yaml/schema.ts index b53fc14a3e86..8db0c67cd943 100644 --- a/packages/utils/src/yaml/schema.ts +++ b/packages/utils/src/yaml/schema.ts @@ -3,9 +3,7 @@ import yml, {FAILSAFE_SCHEMA, Type} from "js-yaml"; import {intType} from "./int.js"; export const schema = FAILSAFE_SCHEMA.extend({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access implicit: [yml.types.null as Type, yml.types.bool as Type, intType, yml.types.float as Type], explicit: [], }); diff --git a/packages/utils/test/perf/bytes.test.ts b/packages/utils/test/perf/bytes.test.ts index 649ea0b51e3e..6a1e96ab1579 100644 --- a/packages/utils/test/perf/bytes.test.ts +++ b/packages/utils/test/perf/bytes.test.ts @@ -3,7 +3,7 @@ import {toHex, toRootHex} from "../../src/bytes/nodejs.js"; import {toHex as browserToHex, toRootHex as browserToRootHex} from "../../src/bytes/browser.js"; import {toHexString} from "../../src/bytes.js"; -describe("bytes utils", function () { +describe("bytes utils", () => { const runsFactor = 1000; const blockRoot = new Uint8Array(Array.from({length: 32}, (_, i) => i)); diff --git a/packages/utils/test/unit/err.test.ts b/packages/utils/test/unit/err.test.ts index 94cebe3ed1a1..7a08ebbc4319 100644 --- a/packages/utils/test/unit/err.test.ts +++ b/packages/utils/test/unit/err.test.ts @@ -60,7 +60,7 @@ describe("Result Err", () => { try { await mapOkResultsAsync([], async () => [0]); throw Error("did not throw"); - } catch (e) { + } catch (_e) { // Ok } }); diff --git a/packages/utils/test/unit/math.test.ts b/packages/utils/test/unit/math.test.ts index 6827fea2bbb0..e324714600b1 100644 --- a/packages/utils/test/unit/math.test.ts +++ b/packages/utils/test/unit/math.test.ts @@ -1,7 +1,7 @@ import {describe, it, expect} from "vitest"; import {bigIntMin, bigIntMax, intDiv, intSqrt, bigIntSqrt} from "../../src/index.js"; -describe("util/maths", function () { +describe("util/maths", () => { describe("bigIntMin", () => { it("if a is lt should return a", () => { const a = BigInt(1); diff --git a/packages/utils/test/unit/objects.test.ts b/packages/utils/test/unit/objects.test.ts index 4699a8c6f405..a94ed9213390 100644 --- a/packages/utils/test/unit/objects.test.ts +++ b/packages/utils/test/unit/objects.test.ts @@ -17,8 +17,6 @@ describe("Objects helper", () => { }); }); -/* eslint-disable @typescript-eslint/naming-convention */ - describe("objectToExpectedCase", () => { const testCases: { id: string; diff --git a/packages/utils/test/unit/promise.node.test.ts b/packages/utils/test/unit/promise.node.test.ts index c9f6a3c2f98d..55cbad36b211 100644 --- a/packages/utils/test/unit/promise.node.test.ts +++ b/packages/utils/test/unit/promise.node.test.ts @@ -2,7 +2,7 @@ import {describe, it, expect, vi, beforeEach, afterEach} from "vitest"; import {callFnWhenAwait} from "../../src/promise.js"; // TODO: Need to debug why vi.useFakeTimers() is not working for the browsers -describe("callFnWhenAwait util", function () { +describe("callFnWhenAwait util", () => { beforeEach(() => { vi.useFakeTimers(); }); diff --git a/packages/utils/test/unit/promiserace.test.ts b/packages/utils/test/unit/promiserace.test.ts index 1f31a55014a4..4b20f19f9fba 100644 --- a/packages/utils/test/unit/promiserace.test.ts +++ b/packages/utils/test/unit/promiserace.test.ts @@ -58,9 +58,8 @@ describe("resolveOrRacePromises", () => { const testPromises = timeouts.map((timeMs) => { if (timeMs > 0) { return resolveAfter(`${timeMs}`, timeMs); - } else { - return rejectAfter(`${timeMs}`, -timeMs); } + return rejectAfter(`${timeMs}`, -timeMs); }); const testResults = (await resolveOrRacePromises(testPromises as unknown as NonEmptyArray>, { resolveTimeoutMs: cutoffMs, diff --git a/packages/utils/test/unit/retry.test.ts b/packages/utils/test/unit/retry.test.ts index 12afb7597015..bd77c499a364 100644 --- a/packages/utils/test/unit/retry.test.ts +++ b/packages/utils/test/unit/retry.test.ts @@ -28,7 +28,8 @@ describe("retry", () => { id: "Succeed at the last attempt", fn: async (attempt) => { if (attempt < retries) throw sampleError; - else return sampleResult; + + return sampleResult; }, opts: {retries}, result: sampleResult, diff --git a/packages/utils/test/unit/sleep.test.ts b/packages/utils/test/unit/sleep.test.ts index a887560836eb..ef632fd34f64 100644 --- a/packages/utils/test/unit/sleep.test.ts +++ b/packages/utils/test/unit/sleep.test.ts @@ -2,13 +2,13 @@ import {describe, it, expect} from "vitest"; import {sleep} from "../../src/sleep.js"; import {ErrorAborted} from "../../src/errors.js"; -describe("sleep", function () { - it("Should resolve timeout", async function () { +describe("sleep", () => { + it("Should resolve timeout", async () => { const controller = new AbortController(); await sleep(0, controller.signal); }); - it("Should abort timeout with signal", async function () { + it("Should abort timeout with signal", async () => { const controller = new AbortController(); setTimeout(() => controller.abort(), 10); @@ -17,7 +17,7 @@ describe("sleep", function () { await expect(sleep(sleepTime, controller.signal)).rejects.toThrow(ErrorAborted); }); - it("Should abort timeout with already aborted signal", async function () { + it("Should abort timeout with already aborted signal", async () => { const controller = new AbortController(); controller.abort(); diff --git a/packages/utils/test/unit/timeout.test.ts b/packages/utils/test/unit/timeout.test.ts index b8844355effb..7b4b1eb883be 100644 --- a/packages/utils/test/unit/timeout.test.ts +++ b/packages/utils/test/unit/timeout.test.ts @@ -2,7 +2,7 @@ import {describe, it, expect, afterEach} from "vitest"; import {withTimeout} from "../../src/timeout.js"; import {ErrorAborted, TimeoutError} from "../../src/errors.js"; -describe("withTimeout", function () { +describe("withTimeout", () => { const data = "DATA"; const shortTimeoutMs = 10; const longTimeoutMs = 5000; @@ -27,19 +27,19 @@ describe("withTimeout", function () { return returnValue; } - it("Should resolve timeout", async function () { + it("Should resolve timeout", async () => { const res = await withTimeout(() => pause(shortTimeoutMs, data), longTimeoutMs); expect(res).toBe(data); }); - it("Should resolve timeout with not triggered signal", async function () { + it("Should resolve timeout with not triggered signal", async () => { const controller = new AbortController(); const res = await withTimeout(() => pause(shortTimeoutMs, data), longTimeoutMs, controller.signal); expect(res).toBe(data); }); - it("Should abort timeout with triggered signal", async function () { + it("Should abort timeout with triggered signal", async () => { const controller = new AbortController(); setTimeout(() => controller.abort(), shortTimeoutMs); @@ -48,11 +48,11 @@ describe("withTimeout", function () { ); }); - it("Should timeout with no signal", async function () { + it("Should timeout with no signal", async () => { await expect(withTimeout(() => pause(longTimeoutMs, data), shortTimeoutMs)).rejects.toThrow(TimeoutError); }); - it("Should timeout with not triggered signal", async function () { + it("Should timeout with not triggered signal", async () => { const controller = new AbortController(); await expect(withTimeout(() => pause(longTimeoutMs, data), shortTimeoutMs, controller.signal)).rejects.toThrow( @@ -60,7 +60,7 @@ describe("withTimeout", function () { ); }); - it("Should abort timeout with already aborted signal", async function () { + it("Should abort timeout with already aborted signal", async () => { const controller = new AbortController(); controller.abort(); diff --git a/packages/validator/package.json b/packages/validator/package.json index 65ed656f010d..9cbb560965fd 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/validator", - "version": "1.22.0", + "version": "1.23.0", "description": "A Typescript implementation of the validator client", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -25,8 +25,8 @@ "build:watch": "yarn run build --watch", "check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"", "check-types": "tsc", - "lint": "eslint --color --ext .ts src/ test/", - "lint:fix": "yarn run lint --fix", + "lint": "biome check src/ test/", + "lint:fix": "yarn run lint --write", "test:unit": "vitest --run --dir test/unit/", "test": "yarn test:unit && yarn test:e2e", "test:spec": "vitest --run --config vitest.spec.config.ts --dir test/spec/", @@ -45,19 +45,19 @@ "blockchain" ], "dependencies": { - "@chainsafe/blst": "^2.0.3", - "@chainsafe/ssz": "^0.17.1", - "@lodestar/api": "^1.22.0", - "@lodestar/config": "^1.22.0", - "@lodestar/db": "^1.22.0", - "@lodestar/params": "^1.22.0", - "@lodestar/state-transition": "^1.22.0", - "@lodestar/types": "^1.22.0", - "@lodestar/utils": "^1.22.0", + "@chainsafe/blst": "^2.2.0", + "@chainsafe/ssz": "^0.18.0", + "@lodestar/api": "^1.23.0", + "@lodestar/config": "^1.23.0", + "@lodestar/db": "^1.23.0", + "@lodestar/params": "^1.23.0", + "@lodestar/state-transition": "^1.23.0", + "@lodestar/types": "^1.23.0", + "@lodestar/utils": "^1.23.0", "strict-event-emitter-types": "^2.0.0" }, "devDependencies": { - "@lodestar/test-utils": "^1.22.0", + "@lodestar/test-utils": "^1.23.0", "bigint-buffer": "^1.1.5", "rimraf": "^4.4.1" } diff --git a/packages/validator/src/buckets.ts b/packages/validator/src/buckets.ts index df30370b8810..3313f7b5ae35 100644 --- a/packages/validator/src/buckets.ts +++ b/packages/validator/src/buckets.ts @@ -17,11 +17,11 @@ export enum Bucket { export function getBucketNameByValue(enumValue: T): keyof typeof Bucket { const keys = Object.keys(Bucket).filter((x) => { - if (isNaN(parseInt(x))) { - return Bucket[x as keyof typeof Bucket] == enumValue; - } else { - return false; + if (Number.isNaN(parseInt(x))) { + return Bucket[x as keyof typeof Bucket] === enumValue; } + + return false; }) as (keyof typeof Bucket)[]; if (keys.length > 0) { return keys[0]; diff --git a/packages/validator/src/genesis.ts b/packages/validator/src/genesis.ts index 3156acee689f..7fc3e20673bc 100644 --- a/packages/validator/src/genesis.ts +++ b/packages/validator/src/genesis.ts @@ -6,7 +6,6 @@ import {ApiClient} from "@lodestar/api"; const WAITING_FOR_GENESIS_POLL_MS = 12 * 1000; export async function waitForGenesis(api: ApiClient, logger: Logger, signal?: AbortSignal): Promise { - // eslint-disable-next-line no-constant-condition while (true) { try { return (await api.beacon.getGenesis()).value(); diff --git a/packages/validator/src/metrics.ts b/packages/validator/src/metrics.ts index a437328e8d5f..1f49c038a50d 100644 --- a/packages/validator/src/metrics.ts +++ b/packages/validator/src/metrics.ts @@ -1,3 +1,4 @@ +import {routes} from "@lodestar/api"; import {MetricsRegisterExtra} from "@lodestar/utils"; export enum MessageSource { @@ -25,7 +26,6 @@ export type LodestarGitData = { /** * A collection of metrics used by the validator client */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function getMetrics(register: MetricsRegisterExtra, gitData: LodestarGitData) { // Using function style instead of class to prevent having to re-declare all MetricsPrometheus types. @@ -39,6 +39,15 @@ export function getMetrics(register: MetricsRegisterExtra, gitData: LodestarGitD .set(gitData, 1); return { + defaultConfiguration: register.gauge<{ + builderSelection: routes.validator.BuilderSelection; + broadcastValidation: routes.beacon.BroadcastValidation; + }>({ + name: "vc_default_configuration", + help: "Default validator configuration", + labelNames: ["builderSelection", "broadcastValidation"], + }), + // Attestation journey: // - Wait for block or 1/3, call prepare attestation // - Get attestation, sign, call publish diff --git a/packages/validator/src/services/attestation.ts b/packages/validator/src/services/attestation.ts index 927bd3d92bb4..7f0dffa3e970 100644 --- a/packages/validator/src/services/attestation.ts +++ b/packages/validator/src/services/attestation.ts @@ -1,8 +1,7 @@ -import {toHexString} from "@chainsafe/ssz"; import {BLSSignature, phase0, Slot, ssz, Attestation, SignedAggregateAndProof} from "@lodestar/types"; import {ForkSeq} from "@lodestar/params"; import {computeEpochAtSlot, isAggregatorFromCommitteeLength} from "@lodestar/state-transition"; -import {prettyBytes, sleep} from "@lodestar/utils"; +import {prettyBytes, sleep, toRootHex} from "@lodestar/utils"; import {ApiClient, routes} from "@lodestar/api"; import {ChainForkConfig} from "@lodestar/config"; import {IClock, LoggerVc} from "../util/index.js"; @@ -195,7 +194,7 @@ export class AttestationService { duties: AttDutyAndProof[] ): Promise { const signedAttestations: Attestation[] = []; - const headRootHex = toHexString(attestationNoCommittee.beaconBlockRoot); + const headRootHex = toRootHex(attestationNoCommittee.beaconBlockRoot); const currentEpoch = computeEpochAtSlot(slot); const isPostElectra = currentEpoch >= this.config.ELECTRA_FORK_EPOCH; diff --git a/packages/validator/src/services/block.ts b/packages/validator/src/services/block.ts index c3acf19c1669..cb295450e96b 100644 --- a/packages/validator/src/services/block.ts +++ b/packages/validator/src/services/block.ts @@ -265,18 +265,17 @@ export class BlockProposingService { debugLogCtx, builderSelection ); - } else { - Object.assign(debugLogCtx, {api: "produceBlindedBlock"}); - const res = await this.api.validator.produceBlindedBlock({slot, randaoReveal, graffiti}); - const {version} = res.meta(); - const executionPayloadSource = ProducedBlockSource.builder; - - return parseProduceBlockResponse( - {data: res.value(), executionPayloadBlinded: true, executionPayloadSource, version}, - debugLogCtx, - builderSelection - ); } + Object.assign(debugLogCtx, {api: "produceBlindedBlock"}); + const res = await this.api.validator.produceBlindedBlock({slot, randaoReveal, graffiti}); + const {version} = res.meta(); + const executionPayloadSource = ProducedBlockSource.builder; + + return parseProduceBlockResponse( + {data: res.value(), executionPayloadBlinded: true, executionPayloadSource, version}, + debugLogCtx, + builderSelection + ); }; } @@ -311,26 +310,26 @@ function parseProduceBlockResponse( executionPayloadSource, debugLogCtx, } as FullOrBlindedBlockWithContents & DebugLogCtx; - } else { - const data = response.data; - if (isBlockContents(data)) { - return { - block: data.block, - contents: {blobs: data.blobs, kzgProofs: data.kzgProofs}, - version: response.version, - executionPayloadBlinded: false, - executionPayloadSource, - debugLogCtx, - } as FullOrBlindedBlockWithContents & DebugLogCtx; - } else { - return { - block: response.data, - contents: null, - version: response.version, - executionPayloadBlinded: false, - executionPayloadSource, - debugLogCtx, - } as FullOrBlindedBlockWithContents & DebugLogCtx; - } } + + const data = response.data; + if (isBlockContents(data)) { + return { + block: data.block, + contents: {blobs: data.blobs, kzgProofs: data.kzgProofs}, + version: response.version, + executionPayloadBlinded: false, + executionPayloadSource, + debugLogCtx, + } as FullOrBlindedBlockWithContents & DebugLogCtx; + } + + return { + block: response.data, + contents: null, + version: response.version, + executionPayloadBlinded: false, + executionPayloadSource, + debugLogCtx, + } as FullOrBlindedBlockWithContents & DebugLogCtx; } diff --git a/packages/validator/src/services/chainHeaderTracker.ts b/packages/validator/src/services/chainHeaderTracker.ts index 845743264d0b..1c0b0d9a56d8 100644 --- a/packages/validator/src/services/chainHeaderTracker.ts +++ b/packages/validator/src/services/chainHeaderTracker.ts @@ -1,6 +1,5 @@ -import {fromHexString} from "@chainsafe/ssz"; import {ApiClient, routes} from "@lodestar/api"; -import {Logger} from "@lodestar/utils"; +import {Logger, fromHex} from "@lodestar/utils"; import {Slot, Root, RootHex} from "@lodestar/types"; import {GENESIS_SLOT} from "@lodestar/params"; import {ValidatorEvent, ValidatorEventEmitter} from "./emitter.js"; @@ -64,7 +63,7 @@ export class ChainHeaderTracker { const {message} = event; const {slot, block, previousDutyDependentRoot, currentDutyDependentRoot} = message; this.headBlockSlot = slot; - this.headBlockRoot = fromHexString(block); + this.headBlockRoot = fromHex(block); const headEventData = { slot: this.headBlockSlot, diff --git a/packages/validator/src/services/doppelgangerService.ts b/packages/validator/src/services/doppelgangerService.ts index 5435c5aed37f..aa43d7bba55b 100644 --- a/packages/validator/src/services/doppelgangerService.ts +++ b/packages/validator/src/services/doppelgangerService.ts @@ -1,7 +1,6 @@ -import {fromHexString} from "@chainsafe/ssz"; import {Epoch, ValidatorIndex} from "@lodestar/types"; import {ApiClient, routes} from "@lodestar/api"; -import {Logger, sleep, truncBytes} from "@lodestar/utils"; +import {Logger, fromHex, sleep, truncBytes} from "@lodestar/utils"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; import {ISlashingProtection} from "../slashingProtection/index.js"; import {ProcessShutdownCallback, PubkeyHex} from "../types.js"; @@ -69,7 +68,7 @@ export class DoppelgangerService { if (remainingEpochs > 0) { const previousEpoch = currentEpoch - 1; const attestedInPreviousEpoch = await this.slashingProtection.hasAttestedInEpoch( - fromHexString(pubkeyHex), + fromHex(pubkeyHex), previousEpoch ); @@ -276,11 +275,12 @@ export class DoppelgangerService { function getStatus(state: DoppelgangerState | undefined): DoppelgangerStatus { if (!state) { return DoppelgangerStatus.Unknown; - } else if (state.remainingEpochs <= 0) { + } + if (state.remainingEpochs <= 0) { return DoppelgangerStatus.VerifiedSafe; - } else if (state.remainingEpochs === REMAINING_EPOCHS_IF_DOPPELGANGER) { + } + if (state.remainingEpochs === REMAINING_EPOCHS_IF_DOPPELGANGER) { return DoppelgangerStatus.DoppelgangerDetected; - } else { - return DoppelgangerStatus.Unverified; } + return DoppelgangerStatus.Unverified; } diff --git a/packages/validator/src/services/emitter.ts b/packages/validator/src/services/emitter.ts index 19f9ac1de54a..2072acba6219 100644 --- a/packages/validator/src/services/emitter.ts +++ b/packages/validator/src/services/emitter.ts @@ -1,4 +1,4 @@ -import {EventEmitter} from "events"; +import {EventEmitter} from "node:events"; import {StrictEventEmitter} from "strict-event-emitter-types"; import {Slot} from "@lodestar/types"; import {HeadEventData} from "./chainHeaderTracker.js"; diff --git a/packages/validator/src/services/externalSignerSync.ts b/packages/validator/src/services/externalSignerSync.ts index 2f1880dda1bb..2f6828d9e09b 100644 --- a/packages/validator/src/services/externalSignerSync.ts +++ b/packages/validator/src/services/externalSignerSync.ts @@ -1,8 +1,7 @@ -import {fromHexString} from "@chainsafe/ssz"; import {PublicKey} from "@chainsafe/blst"; import {ChainForkConfig} from "@lodestar/config"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {toPrintableUrl} from "@lodestar/utils"; +import {fromHex, toPrintableUrl} from "@lodestar/utils"; import {LoggerVc} from "../util/index.js"; import {externalSignerGetKeys} from "../util/externalSignerClient.js"; @@ -77,7 +76,7 @@ export function pollExternalSignerPubkeys( function assertValidPubkeysHex(pubkeysHex: string[]): void { for (const pubkeyHex of pubkeysHex) { - const pubkeyBytes = fromHexString(pubkeyHex); + const pubkeyBytes = fromHex(pubkeyHex); PublicKey.fromBytes(pubkeyBytes, true); } } diff --git a/packages/validator/src/services/indices.ts b/packages/validator/src/services/indices.ts index ec5155322918..ff8d1d46ab26 100644 --- a/packages/validator/src/services/indices.ts +++ b/packages/validator/src/services/indices.ts @@ -5,10 +5,12 @@ import {batchItems} from "../util/index.js"; import {Metrics} from "../metrics.js"; /** - * URLs have a limitation on size, adding an unbounded num of pubkeys will break the request. - * For reasoning on the specific number see: https://github.com/ethereum/beacon-APIs/pull/328 + * This is to prevent the "Request body is too large" issue for http post. + * Typical servers accept up to 1MB (2 ** 20 bytes) of request body, for example fastify and nginx. + * A hex encoded public key with "0x"-prefix has a size of 98 bytes + 2 bytes to account for commas + * and other JSON padding. `Math.floor(2 ** 20 / 100) == 10485`, we can send up to ~10k keys per request. */ -const PUBKEYS_PER_REQUEST = 64; +const PUBKEYS_PER_REQUEST = 10_000; // To assist with readability type PubkeyHex = string; @@ -18,7 +20,6 @@ type SimpleValidatorStatus = "pending" | "active" | "exited" | "withdrawn"; const statusToSimpleStatusMapping = (status: routes.beacon.ValidatorStatus): SimpleValidatorStatus => { switch (status) { - case "active": case "active_exiting": case "active_slashed": case "active_ongoing": @@ -108,7 +109,7 @@ export class IndicesService { } // Query the remote BN to resolve a pubkey to a validator index. - // support up to 1000 pubkeys per poll + // support up to 10k pubkeys per poll const pubkeysHexBatches = batchItems(pubkeysHexToDiscover, {batchSize: PUBKEYS_PER_REQUEST}); const newIndices: number[] = []; @@ -123,7 +124,7 @@ export class IndicesService { } private async fetchValidatorIndices(pubkeysHex: string[]): Promise { - const validators = (await this.api.beacon.getStateValidators({stateId: "head", validatorIds: pubkeysHex})).value(); + const validators = (await this.api.beacon.postStateValidators({stateId: "head", validatorIds: pubkeysHex})).value(); const newIndices = []; diff --git a/packages/validator/src/services/validatorStore.ts b/packages/validator/src/services/validatorStore.ts index c6130f1fab95..dfd856b18d13 100644 --- a/packages/validator/src/services/validatorStore.ts +++ b/packages/validator/src/services/validatorStore.ts @@ -1,4 +1,4 @@ -import {BitArray, fromHexString, toHexString} from "@chainsafe/ssz"; +import {BitArray} from "@chainsafe/ssz"; import {SecretKey} from "@chainsafe/blst"; import { computeEpochAtSlot, @@ -42,7 +42,7 @@ import { SignedAggregateAndProof, } from "@lodestar/types"; import {routes} from "@lodestar/api"; -import {toPubkeyHex} from "@lodestar/utils"; +import {fromHex, toPubkeyHex, toRootHex} from "@lodestar/utils"; import {ISlashingProtection} from "../slashingProtection/index.js"; import {PubkeyHex} from "../types.js"; import {externalSignerPostSignature, SignableMessageType, SignableMessage} from "../util/externalSignerClient.js"; @@ -247,7 +247,7 @@ export class ValidatorStore { throw Error(`Validator pubkey ${pubkeyHex} not known`); } // This should directly modify data in the map - delete validatorData["feeRecipient"]; + delete validatorData.feeRecipient; } getGraffiti(pubkeyHex: PubkeyHex): string | undefined { @@ -267,14 +267,14 @@ export class ValidatorStore { if (validatorData === undefined) { throw Error(`Validator pubkey ${pubkeyHex} not known`); } - delete validatorData["graffiti"]; + delete validatorData.graffiti; } getBuilderSelectionParams(pubkeyHex: PubkeyHex): {selection: routes.validator.BuilderSelection; boostFactor: bigint} { const selection = - (this.validators.get(pubkeyHex)?.builder || {}).selection ?? this.defaultProposerConfig.builder.selection; + this.validators.get(pubkeyHex)?.builder?.selection ?? this.defaultProposerConfig.builder.selection; - let boostFactor; + let boostFactor: bigint; switch (selection) { case routes.validator.BuilderSelection.Default: // Default value slightly favors local block to improve censorship resistance of Ethereum @@ -284,7 +284,7 @@ export class ValidatorStore { case routes.validator.BuilderSelection.MaxProfit: boostFactor = - (this.validators.get(pubkeyHex)?.builder || {}).boostFactor ?? this.defaultProposerConfig.builder.boostFactor; + this.validators.get(pubkeyHex)?.builder?.boostFactor ?? this.defaultProposerConfig.builder.boostFactor; break; case routes.validator.BuilderSelection.BuilderAlways: @@ -386,7 +386,7 @@ export class ValidatorStore { async addSigner(signer: Signer, valProposerConfig?: ValidatorProposerConfig): Promise { const pubkey = getSignerPubkeyHex(signer); - const proposerConfig = (valProposerConfig?.proposerConfig ?? {})[pubkey]; + const proposerConfig = valProposerConfig?.proposerConfig?.[pubkey]; const builderBoostFactor = proposerConfig?.builder?.boostFactor; if (builderBoostFactor !== undefined && builderBoostFactor > MAX_BUILDER_BOOST_FACTOR) { throw Error(`Invalid builderBoostFactor=${builderBoostFactor} > MAX_BUILDER_BOOST_FACTOR for pubkey=${pubkey}`); @@ -459,8 +459,8 @@ export class ValidatorStore { logger?.debug("Signing the block proposal", { slot: signingSlot, - blockRoot: toHexString(blockRoot), - signingRoot: toHexString(signingRoot), + blockRoot: toRootHex(blockRoot), + signingRoot: toRootHex(signingRoot), }); try { @@ -531,20 +531,20 @@ export class ValidatorStore { data: attestationData, }; - if (this.config.getForkSeq(duty.slot) >= ForkSeq.electra) { + if (this.config.getForkSeq(signingSlot) >= ForkSeq.electra) { return { aggregationBits: BitArray.fromSingleBit(duty.committeeLength, duty.validatorCommitteeIndex), data: attestationData, signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage), committeeBits: BitArray.fromSingleBit(MAX_COMMITTEES_PER_SLOT, duty.committeeIndex), }; - } else { - return { - aggregationBits: BitArray.fromSingleBit(duty.committeeLength, duty.validatorCommitteeIndex), - data: attestationData, - signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage), - } as phase0.Attestation; } + + return { + aggregationBits: BitArray.fromSingleBit(duty.committeeLength, duty.validatorCommitteeIndex), + data: attestationData, + signature: await this.getSignature(duty.pubkey, signingRoot, signingSlot, signableMessage), + } as phase0.Attestation; } async signAggregateAndProof( @@ -562,13 +562,13 @@ export class ValidatorStore { const signingSlot = aggregate.data.slot; const domain = this.config.getDomain(signingSlot, DOMAIN_AGGREGATE_AND_PROOF); - const signingRoot = - this.config.getForkSeq(duty.slot) >= ForkSeq.electra - ? computeSigningRoot(ssz.electra.AggregateAndProof, aggregateAndProof, domain) - : computeSigningRoot(ssz.phase0.AggregateAndProof, aggregateAndProof, domain); + const isPostElectra = this.config.getForkSeq(signingSlot) >= ForkSeq.electra; + const signingRoot = isPostElectra + ? computeSigningRoot(ssz.electra.AggregateAndProof, aggregateAndProof, domain) + : computeSigningRoot(ssz.phase0.AggregateAndProof, aggregateAndProof, domain); const signableMessage: SignableMessage = { - type: SignableMessageType.AGGREGATE_AND_PROOF, + type: isPostElectra ? SignableMessageType.AGGREGATE_AND_PROOF_V2 : SignableMessageType.AGGREGATE_AND_PROOF, data: aggregateAndProof, }; @@ -693,8 +693,8 @@ export class ValidatorStore { regAttributes: {feeRecipient: Eth1Address; gasLimit: number}, _slot: Slot ): Promise { - const pubkey = typeof pubkeyMaybeHex === "string" ? fromHexString(pubkeyMaybeHex) : pubkeyMaybeHex; - const feeRecipient = fromHexString(regAttributes.feeRecipient); + const pubkey = typeof pubkeyMaybeHex === "string" ? fromHex(pubkeyMaybeHex) : pubkeyMaybeHex; + const feeRecipient = fromHex(regAttributes.feeRecipient); const {gasLimit} = regAttributes; const validatorRegistration: bellatrix.ValidatorRegistrationV1 = { @@ -731,15 +731,14 @@ export class ValidatorStore { const builderData = validatorData?.builderData; if (builderData?.regFullKey === regFullKey) { return builderData.validatorRegistration; - } else { - const validatorRegistration = await this.signValidatorRegistration(pubkeyMaybeHex, regAttributes, slot); - // If pubkeyHex was actually registered, then update the regData - if (validatorData !== undefined) { - validatorData.builderData = {validatorRegistration, regFullKey}; - this.validators.set(pubkeyHex, validatorData); - } - return validatorRegistration; } + const validatorRegistration = await this.signValidatorRegistration(pubkeyMaybeHex, regAttributes, slot); + // If pubkeyHex was actually registered, then update the regData + if (validatorData !== undefined) { + validatorData.builderData = {validatorRegistration, regFullKey}; + this.validators.set(pubkeyHex, validatorData); + } + return validatorRegistration; } private async getSignature( @@ -748,7 +747,7 @@ export class ValidatorStore { signingSlot: Slot, signableMessage: SignableMessage ): Promise { - // TODO: Refactor indexing to not have to run toHexString() on the pubkey every time + // TODO: Refactor indexing to not have to run toHex() on the pubkey every time const pubkeyHex = typeof pubkey === "string" ? pubkey : toPubkeyHex(pubkey); const signer = this.validators.get(pubkeyHex)?.signer; @@ -775,7 +774,7 @@ export class ValidatorStore { signingSlot, signableMessage ); - return fromHexString(signatureHex); + return fromHex(signatureHex); } catch (e) { this.metrics?.remoteSignErrors.inc(); throw e; @@ -787,7 +786,7 @@ export class ValidatorStore { } private getSignerAndPubkeyHex(pubkey: BLSPubkeyMaybeHex): [Signer, string] { - // TODO: Refactor indexing to not have to run toHexString() on the pubkey every time + // TODO: Refactor indexing to not have to run toHex() on the pubkey every time const pubkeyHex = typeof pubkey === "string" ? pubkey : toPubkeyHex(pubkey); const signer = this.validators.get(pubkeyHex)?.signer; if (!signer) { @@ -802,8 +801,8 @@ export class ValidatorStore { throw Error(`Inconsistent duties during signing: duty.slot ${duty.slot} != att.slot ${data.slot}`); } - const isPostElectra = this.config.getForkSeq(duty.slot) >= ForkSeq.electra; - if (!isPostElectra && duty.committeeIndex != data.index) { + const isPostElectra = this.config.getForkSeq(data.slot) >= ForkSeq.electra; + if (!isPostElectra && duty.committeeIndex !== data.index) { throw Error( `Inconsistent duties during signing: duty.committeeIndex ${duty.committeeIndex} != att.committeeIndex ${data.index}` ); @@ -824,7 +823,7 @@ export class ValidatorStore { function getSignerPubkeyHex(signer: Signer): PubkeyHex { switch (signer.type) { case SignerType.Local: - return toHexString(signer.secretKey.toPublicKey().toBytes()); + return toPubkeyHex(signer.secretKey.toPublicKey().toBytes()); case SignerType.Remote: if (!isValidatePubkeyHex(signer.pubkey)) { diff --git a/packages/validator/src/slashingProtection/attestation/errors.ts b/packages/validator/src/slashingProtection/attestation/errors.ts index 7d4b0978d097..34e11c190c8a 100644 --- a/packages/validator/src/slashingProtection/attestation/errors.ts +++ b/packages/validator/src/slashingProtection/attestation/errors.ts @@ -63,8 +63,4 @@ type InvalidAttestationErrorType = minTargetEpoch: Epoch; }; -export class InvalidAttestationError extends LodestarError { - constructor(type: InvalidAttestationErrorType) { - super(type); - } -} +export class InvalidAttestationError extends LodestarError {} diff --git a/packages/validator/src/slashingProtection/attestation/index.ts b/packages/validator/src/slashingProtection/attestation/index.ts index 681bdf5f0b13..f0d3a0bca172 100644 --- a/packages/validator/src/slashingProtection/attestation/index.ts +++ b/packages/validator/src/slashingProtection/attestation/index.ts @@ -39,7 +39,7 @@ export class SlashingProtectionAttestationService { async checkAndInsertAttestation(pubKey: BLSPubkey, attestation: SlashingProtectionAttestation): Promise { const safeStatus = await this.checkAttestation(pubKey, attestation); - if (safeStatus != SafeStatus.SAME_DATA) { + if (safeStatus !== SafeStatus.SAME_DATA) { await this.insertAttestation(pubKey, attestation); } @@ -63,13 +63,12 @@ export class SlashingProtectionAttestationService { // Interchange format allows for attestations without signing_root, then assume root is equal if (isEqualNonZeroRoot(sameTargetAtt.signingRoot, attestation.signingRoot)) { return SafeStatus.SAME_DATA; - } else { - throw new InvalidAttestationError({ - code: InvalidAttestationErrorCode.DOUBLE_VOTE, - attestation: attestation, - prev: sameTargetAtt, - }); } + throw new InvalidAttestationError({ + code: InvalidAttestationErrorCode.DOUBLE_VOTE, + attestation: attestation, + prev: sameTargetAtt, + }); } // Check for a surround vote diff --git a/packages/validator/src/slashingProtection/block/errors.ts b/packages/validator/src/slashingProtection/block/errors.ts index be3696bba700..e7868881627b 100644 --- a/packages/validator/src/slashingProtection/block/errors.ts +++ b/packages/validator/src/slashingProtection/block/errors.ts @@ -25,8 +25,4 @@ type InvalidBlockErrorType = minSlot: Slot; }; -export class InvalidBlockError extends LodestarError { - constructor(type: InvalidBlockErrorType) { - super(type); - } -} +export class InvalidBlockError extends LodestarError {} diff --git a/packages/validator/src/slashingProtection/block/index.ts b/packages/validator/src/slashingProtection/block/index.ts index bab380f99809..385575e82a0a 100644 --- a/packages/validator/src/slashingProtection/block/index.ts +++ b/packages/validator/src/slashingProtection/block/index.ts @@ -24,7 +24,7 @@ export class SlashingProtectionBlockService { async checkAndInsertBlockProposal(pubkey: BLSPubkey, block: SlashingProtectionBlock): Promise { const safeStatus = await this.checkBlockProposal(pubkey, block); - if (safeStatus != SafeStatus.SAME_DATA) { + if (safeStatus !== SafeStatus.SAME_DATA) { await this.insertBlockProposal(pubkey, block); } @@ -41,13 +41,13 @@ export class SlashingProtectionBlockService { // Interchange format allows for blocks without signing_root, then assume root is equal if (isEqualNonZeroRoot(sameSlotBlock.signingRoot, block.signingRoot)) { return SafeStatus.SAME_DATA; - } else { - throw new InvalidBlockError({ - code: InvalidBlockErrorCode.DOUBLE_BLOCK_PROPOSAL, - block, - block2: sameSlotBlock, - }); } + + throw new InvalidBlockError({ + code: InvalidBlockErrorCode.DOUBLE_BLOCK_PROPOSAL, + block, + block2: sameSlotBlock, + }); } // Refuse to sign any block with slot <= min(b.slot for b in data.signed_blocks if b.pubkey == proposer_pubkey), diff --git a/packages/validator/src/slashingProtection/interchange/errors.ts b/packages/validator/src/slashingProtection/interchange/errors.ts index 63771df357c2..d03215f0d930 100644 --- a/packages/validator/src/slashingProtection/interchange/errors.ts +++ b/packages/validator/src/slashingProtection/interchange/errors.ts @@ -12,8 +12,4 @@ type InterchangeErrorErrorType = | {code: InterchangeErrorErrorCode.UNSUPPORTED_VERSION; version: string} | {code: InterchangeErrorErrorCode.GENESIS_VALIDATOR_MISMATCH; root: Root; expectedRoot: Root}; -export class InterchangeError extends LodestarError { - constructor(type: InterchangeErrorErrorType) { - super(type); - } -} +export class InterchangeError extends LodestarError {} diff --git a/packages/validator/src/slashingProtection/interchange/formats/completeV4.ts b/packages/validator/src/slashingProtection/interchange/formats/completeV4.ts index 66aa31c52194..26210390a272 100644 --- a/packages/validator/src/slashingProtection/interchange/formats/completeV4.ts +++ b/packages/validator/src/slashingProtection/interchange/formats/completeV4.ts @@ -1,6 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString, toHexString} from "@chainsafe/ssz"; -import {toPubkeyHex} from "@lodestar/utils"; +import {fromHex, toPubkeyHex, toRootHex} from "@lodestar/utils"; import {InterchangeLodestar} from "../types.js"; import {fromOptionalHexString, numToString, toOptionalHexString} from "../../utils.js"; @@ -91,7 +89,7 @@ export function serializeInterchangeCompleteV4({ metadata: { interchange_format: "complete", interchange_format_version: "4", - genesis_validators_root: toHexString(genesisValidatorsRoot), + genesis_validators_root: toRootHex(genesisValidatorsRoot), }, data: data.map((validator) => ({ pubkey: toPubkeyHex(validator.pubkey), @@ -110,9 +108,9 @@ export function serializeInterchangeCompleteV4({ export function parseInterchangeCompleteV4(interchange: InterchangeCompleteV4): InterchangeLodestar { return { - genesisValidatorsRoot: fromHexString(interchange.metadata.genesis_validators_root), + genesisValidatorsRoot: fromHex(interchange.metadata.genesis_validators_root), data: interchange.data.map((validator) => ({ - pubkey: fromHexString(validator.pubkey), + pubkey: fromHex(validator.pubkey), signedBlocks: validator.signed_blocks.map((block) => ({ slot: parseInt(block.slot, 10), signingRoot: fromOptionalHexString(block.signing_root), diff --git a/packages/validator/src/slashingProtection/interchange/formats/v5.ts b/packages/validator/src/slashingProtection/interchange/formats/v5.ts index 1c7f67b706a5..88e2ce70fe07 100644 --- a/packages/validator/src/slashingProtection/interchange/formats/v5.ts +++ b/packages/validator/src/slashingProtection/interchange/formats/v5.ts @@ -1,6 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import {fromHexString, toHexString} from "@chainsafe/ssz"; -import {toPubkeyHex} from "@lodestar/utils"; +import {fromHex, toPubkeyHex, toRootHex} from "@lodestar/utils"; import {InterchangeLodestar} from "../types.js"; import {fromOptionalHexString, numToString, toOptionalHexString} from "../../utils.js"; @@ -86,7 +84,7 @@ export function serializeInterchangeV5({data, genesisValidatorsRoot}: Interchang return { metadata: { interchange_format_version: "5", - genesis_validators_root: toHexString(genesisValidatorsRoot), + genesis_validators_root: toRootHex(genesisValidatorsRoot), }, data: data.map((validator) => ({ pubkey: toPubkeyHex(validator.pubkey), @@ -105,9 +103,9 @@ export function serializeInterchangeV5({data, genesisValidatorsRoot}: Interchang export function parseInterchangeV5(interchange: InterchangeV5): InterchangeLodestar { return { - genesisValidatorsRoot: fromHexString(interchange.metadata.genesis_validators_root), + genesisValidatorsRoot: fromHex(interchange.metadata.genesis_validators_root), data: interchange.data.map((validator) => ({ - pubkey: fromHexString(validator.pubkey), + pubkey: fromHex(validator.pubkey), signedBlocks: validator.signed_blocks.map((block) => ({ slot: parseInt(block.slot, 10), signingRoot: fromOptionalHexString(block.signing_root), diff --git a/packages/validator/src/slashingProtection/minMaxSurround/errors.ts b/packages/validator/src/slashingProtection/minMaxSurround/errors.ts index fd094df54db3..b6cd31187f57 100644 --- a/packages/validator/src/slashingProtection/minMaxSurround/errors.ts +++ b/packages/validator/src/slashingProtection/minMaxSurround/errors.ts @@ -24,8 +24,4 @@ type SurroundAttestationErrorType = attestation2Target: number; }; -export class SurroundAttestationError extends LodestarError { - constructor(type: SurroundAttestationErrorType) { - super(type); - } -} +export class SurroundAttestationError extends LodestarError {} diff --git a/packages/validator/src/slashingProtection/utils.ts b/packages/validator/src/slashingProtection/utils.ts index f1fc0f24b9ae..adc93bdafee5 100644 --- a/packages/validator/src/slashingProtection/utils.ts +++ b/packages/validator/src/slashingProtection/utils.ts @@ -1,5 +1,5 @@ -import {fromHexString, toHexString} from "@chainsafe/ssz"; import {Epoch, Root, ssz} from "@lodestar/types"; +import {fromHex, toHex, toRootHex} from "@lodestar/utils"; export const blsPubkeyLen = 48; export const ZERO_ROOT = ssz.Root.defaultValue(); @@ -13,11 +13,11 @@ export function isEqualNonZeroRoot(root1: Root, root2: Root): boolean { } export function fromOptionalHexString(hex: string | undefined): Root { - return hex ? fromHexString(hex) : ZERO_ROOT; + return hex ? fromHex(hex) : ZERO_ROOT; } export function toOptionalHexString(root: Root): string | undefined { - return isEqualRoot(root, ZERO_ROOT) ? undefined : toHexString(root); + return isEqualRoot(root, ZERO_ROOT) ? undefined : toRootHex(root); } /** @@ -34,7 +34,7 @@ export function minEpoch(epochs: Epoch[]): Epoch | null { export function uniqueVectorArr(buffers: Uint8Array[]): Uint8Array[] { const bufferStr = new Set(); return buffers.filter((buffer) => { - const str = toHexString(buffer); + const str = toHex(buffer); const seen = bufferStr.has(str); bufferStr.add(str); return !seen; diff --git a/packages/validator/src/util/clock.ts b/packages/validator/src/util/clock.ts index ca29eacd41c2..4b3fc45cd803 100644 --- a/packages/validator/src/util/clock.ts +++ b/packages/validator/src/util/clock.ts @@ -119,17 +119,14 @@ export class Clock implements IClock { if (timeItem === TimeItem.Slot) { if (msFromGenesis >= 0) { return milliSecondsPerSlot - (msFromGenesis % milliSecondsPerSlot); - } else { - return Math.abs(msFromGenesis % milliSecondsPerSlot); - } - } else { - const milliSecondsPerEpoch = SLOTS_PER_EPOCH * milliSecondsPerSlot; - if (msFromGenesis >= 0) { - return milliSecondsPerEpoch - (msFromGenesis % milliSecondsPerEpoch); - } else { - return Math.abs(msFromGenesis % milliSecondsPerEpoch); } + return Math.abs(msFromGenesis % milliSecondsPerSlot); + } + const milliSecondsPerEpoch = SLOTS_PER_EPOCH * milliSecondsPerSlot; + if (msFromGenesis >= 0) { + return milliSecondsPerEpoch - (msFromGenesis % milliSecondsPerEpoch); } + return Math.abs(msFromGenesis % milliSecondsPerEpoch); } } diff --git a/packages/validator/src/util/difference.ts b/packages/validator/src/util/difference.ts index bafc6ff8f42f..b8c3c5e7b089 100644 --- a/packages/validator/src/util/difference.ts +++ b/packages/validator/src/util/difference.ts @@ -1,10 +1,10 @@ -import {toHexString} from "@chainsafe/ssz"; import {Root} from "@lodestar/types"; +import {toHex} from "@lodestar/utils"; /** * Return items included in `next` but not in `prev` */ export function differenceHex(prev: T[], next: T[]): T[] { - const existing = new Set(prev.map((item) => toHexString(item))); - return next.filter((item) => !existing.has(toHexString(item))); + const existing = new Set(prev.map((item) => toHex(item))); + return next.filter((item) => !existing.has(toHex(item))); } diff --git a/packages/validator/src/util/externalSignerClient.ts b/packages/validator/src/util/externalSignerClient.ts index dc1d0d0f1dd5..54c0c16946ad 100644 --- a/packages/validator/src/util/externalSignerClient.ts +++ b/packages/validator/src/util/externalSignerClient.ts @@ -1,18 +1,30 @@ -import {ContainerType, toHexString, ValueOf} from "@chainsafe/ssz"; +import {ContainerType, ValueOf} from "@chainsafe/ssz"; import {fetch} from "@lodestar/api"; -import {phase0, altair, capella, BeaconBlock, BlindedBeaconBlock} from "@lodestar/types"; -import {ForkSeq} from "@lodestar/params"; +import { + phase0, + altair, + capella, + BeaconBlock, + BlindedBeaconBlock, + AggregateAndProof, + sszTypesFor, + ssz, + Slot, + Epoch, + RootHex, + Root, +} from "@lodestar/types"; +import {ForkPreExecution, ForkSeq} from "@lodestar/params"; import {ValidatorRegistrationV1} from "@lodestar/types/bellatrix"; import {BeaconConfig} from "@lodestar/config"; import {computeEpochAtSlot, blindedOrFullBlockToHeader} from "@lodestar/state-transition"; -import {Epoch, Root, RootHex, Slot, ssz} from "@lodestar/types"; +import {toHex, toRootHex} from "@lodestar/utils"; import {PubkeyHex} from "../types.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - export enum SignableMessageType { AGGREGATION_SLOT = "AGGREGATION_SLOT", AGGREGATE_AND_PROOF = "AGGREGATE_AND_PROOF", + AGGREGATE_AND_PROOF_V2 = "AGGREGATE_AND_PROOF_V2", ATTESTATION = "ATTESTATION", BLOCK_V2 = "BLOCK_V2", DEPOSIT = "DEPOSIT", @@ -62,8 +74,9 @@ const SyncAggregatorSelectionDataType = new ContainerType( export type SignableMessage = | {type: SignableMessageType.AGGREGATION_SLOT; data: {slot: Slot}} | {type: SignableMessageType.AGGREGATE_AND_PROOF; data: phase0.AggregateAndProof} + | {type: SignableMessageType.AGGREGATE_AND_PROOF_V2; data: AggregateAndProof} | {type: SignableMessageType.ATTESTATION; data: phase0.AttestationData} - | {type: SignableMessageType.BLOCK_V2; data: BeaconBlock | BlindedBeaconBlock} + | {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} @@ -76,6 +89,7 @@ export type SignableMessage = const requiresForkInfo: Record = { [SignableMessageType.AGGREGATION_SLOT]: true, [SignableMessageType.AGGREGATE_AND_PROOF]: true, + [SignableMessageType.AGGREGATE_AND_PROOF_V2]: true, [SignableMessageType.ATTESTATION]: true, [SignableMessageType.BLOCK_V2]: true, [SignableMessageType.DEPOSIT]: false, @@ -131,17 +145,17 @@ export async function externalSignerPostSignature( const requestObj = serializerSignableMessagePayload(config, signableMessage) as Web3SignerSerializedRequest; requestObj.type = signableMessage.type; - requestObj.signingRoot = toHexString(signingRoot); + requestObj.signingRoot = toRootHex(signingRoot); if (requiresForkInfo[signableMessage.type]) { const forkInfo = config.getForkInfo(signingSlot); requestObj.fork_info = { fork: { - previous_version: toHexString(forkInfo.prevVersion), - current_version: toHexString(forkInfo.version), + previous_version: toHex(forkInfo.prevVersion), + current_version: toHex(forkInfo.version), epoch: String(computeEpochAtSlot(signingSlot)), }, - genesis_validators_root: toHexString(config.genesisValidatorsRoot), + genesis_validators_root: toRootHex(config.genesisValidatorsRoot), }; } @@ -202,6 +216,16 @@ function serializerSignableMessagePayload(config: BeaconConfig, payload: Signabl case SignableMessageType.AGGREGATE_AND_PROOF: return {aggregate_and_proof: ssz.phase0.AggregateAndProof.toJson(payload.data)}; + case SignableMessageType.AGGREGATE_AND_PROOF_V2: { + const fork = config.getForkName(payload.data.aggregate.data.slot); + return { + aggregate_and_proof: { + version: fork.toUpperCase(), + data: sszTypesFor(fork).AggregateAndProof.toJson(payload.data), + }, + }; + } + case SignableMessageType.ATTESTATION: return {attestation: ssz.phase0.AttestationData.toJson(payload.data)}; @@ -217,14 +241,14 @@ function serializerSignableMessagePayload(config: BeaconConfig, payload: Signabl block_header: ssz.phase0.BeaconBlockHeader.toJson(blindedOrFullBlockToHeader(config, payload.data)), }, }; - } else { - return { - beacon_block: { - version, - block: config.getForkTypes(payload.data.slot).BeaconBlock.toJson(payload.data), - }, - }; } + + return { + beacon_block: { + version, + block: config.getForkTypes(payload.data.slot).BeaconBlock.toJson(payload.data), + }, + }; } case SignableMessageType.DEPOSIT: diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 6d6705f512df..16374af2b837 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -5,8 +5,6 @@ export class NotEqualParamsError extends Error {} type ConfigWithPreset = ChainConfig & BeaconPreset; -/* eslint-disable @typescript-eslint/naming-convention */ - /** * Assert localConfig values match externalSpecJson. externalSpecJson may contain more values than localConfig. * @@ -228,10 +226,11 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record { - const validators = (await this.api.beacon.getStateValidators({stateId: "head", validatorIds: [publicKey]})).value(); + const validators = ( + await this.api.beacon.postStateValidators({stateId: "head", validatorIds: [publicKey]}) + ).value(); const validator = validators[0]; if (validator === undefined) { @@ -397,14 +400,14 @@ async function assertEqualGenesis(opts: ValidatorOptions, genesis: Genesis): Pro if (!ssz.Root.equals(genesisValidatorsRoot, nodeGenesisValidatorRoot)) { // this happens when the existing validator db served another network before opts.logger.error("Not the same genesisValidatorRoot", { - expected: toHexString(nodeGenesisValidatorRoot), - actual: toHexString(genesisValidatorsRoot), + expected: toRootHex(nodeGenesisValidatorRoot), + actual: toRootHex(genesisValidatorsRoot), }); throw new NotEqualParamsError("Not the same genesisValidatorRoot"); } } else { await metaDataRepository.setGenesisValidatorsRoot(nodeGenesisValidatorRoot); - opts.logger.info("Persisted genesisValidatorRoot", toHexString(nodeGenesisValidatorRoot)); + opts.logger.info("Persisted genesisValidatorRoot", toRootHex(nodeGenesisValidatorRoot)); } const nodeGenesisTime = genesis.genesisTime; diff --git a/packages/validator/test/e2e/web3signer.test.ts b/packages/validator/test/e2e/web3signer.test.ts index 326dbae8c4f7..855b3dd9f754 100644 --- a/packages/validator/test/e2e/web3signer.test.ts +++ b/packages/validator/test/e2e/web3signer.test.ts @@ -5,14 +5,14 @@ import {computeStartSlotAtEpoch, interopSecretKey, interopSecretKeys} from "@lod import {createBeaconConfig} from "@lodestar/config"; import {genesisData} from "@lodestar/config/networks"; import {getClient, routes} from "@lodestar/api"; -import {ssz} from "@lodestar/types"; +import {ssz, sszTypesFor} from "@lodestar/types"; import {ForkSeq} from "@lodestar/params"; import {getKeystoresStr, StartedExternalSigner, startExternalSigner} from "@lodestar/test-utils"; import {Interchange, ISlashingProtection, Signer, SignerType, ValidatorStore} from "../../src/index.js"; import {IndicesService} from "../../src/services/indices.js"; import {testLogger} from "../utils/logger.js"; -describe("web3signer signature test", function () { +describe("web3signer signature test", () => { vi.setConfig({testTimeout: 60_000, hookTimeout: 60_000}); const altairSlot = 2375711; @@ -82,7 +82,7 @@ describe("web3signer signature test", function () { }); } - it("signRandao", async function () { + it("signRandao", async () => { await assertSameSignature("signRandao", pubkeyBytes, epoch); }); @@ -93,17 +93,27 @@ describe("web3signer signature test", function () { await assertSameSignature("signAttestation", duty, attestationData, epoch); }); - it("signAggregateAndProof", async () => { - const aggregateAndProof = ssz.phase0.AggregateAndProof.defaultValue(); - aggregateAndProof.aggregate.data.slot = duty.slot; - aggregateAndProof.aggregate.data.index = duty.committeeIndex; - await assertSameSignature( - "signAggregateAndProof", - duty, - aggregateAndProof.selectionProof, - aggregateAndProof.aggregate - ); - }); + for (const fork of config.forksAscendingEpochOrder) { + it(`signAggregateAndProof ${fork.name}`, async ({skip}) => { + // Only test till the fork the signer version supports + if (ForkSeq[fork.name] > externalSigner.supportedForkSeq) { + skip(); + return; + } + + const aggregateAndProof = sszTypesFor(fork.name).AggregateAndProof.defaultValue(); + const slot = computeStartSlotAtEpoch(fork.epoch); + aggregateAndProof.aggregate.data.slot = slot; + aggregateAndProof.aggregate.data.index = duty.committeeIndex; + + await assertSameSignature( + "signAggregateAndProof", + {...duty, slot}, + aggregateAndProof.selectionProof, + aggregateAndProof.aggregate + ); + }); + } it("signSyncCommitteeSignature", async () => { const beaconBlockRoot = ssz.phase0.BeaconBlockHeader.defaultValue().bodyRoot; diff --git a/packages/validator/test/spec/params.ts b/packages/validator/test/spec/params.ts index c51a881bfeb3..0f5c707ff1c5 100644 --- a/packages/validator/test/spec/params.ts +++ b/packages/validator/test/spec/params.ts @@ -3,7 +3,6 @@ import {fileURLToPath} from "node:url"; // Global variable __dirname no longer available in ES6 modules. // Solutions: https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Full link: https://github.com/eth2-clients/slashing-protection-interchange-tests/releases/download/v5.1.0/eip-3076-tests-v5.1.0.tar.gz diff --git a/packages/validator/test/unit/services/attestation.test.ts b/packages/validator/test/unit/services/attestation.test.ts index 356503cbdd09..1652af83c5d1 100644 --- a/packages/validator/test/unit/services/attestation.test.ts +++ b/packages/validator/test/unit/services/attestation.test.ts @@ -22,7 +22,7 @@ vi.mock("../../../src/services/emitter.js"); vi.mock("../../../src/services/chainHeaderTracker.js"); vi.mock("../../../src/services/syncingStatusTracker.js"); -describe("AttestationService", function () { +describe("AttestationService", () => { const api = getApiClientStub(); // @ts-expect-error - Mocked class don't need parameters const validatorStore = vi.mocked(new ValidatorStore()); @@ -52,7 +52,6 @@ describe("AttestationService", function () { vi.resetAllMocks(); }); - // eslint-disable-next-line @typescript-eslint/naming-convention const electraConfig: Partial = {ELECTRA_FORK_EPOCH: 0}; const testContexts: [string, AttestationServiceOpts, Partial][] = [ @@ -105,7 +104,7 @@ describe("AttestationService", function () { ]; // Return empty replies to duties service - api.beacon.getStateValidators.mockResolvedValue( + api.beacon.postStateValidators.mockResolvedValue( mockApiResponse({data: [], meta: {executionOptimistic: false, finalized: false}}) ); api.validator.getAttesterDuties.mockResolvedValue( diff --git a/packages/validator/test/unit/services/attestationDuties.test.ts b/packages/validator/test/unit/services/attestationDuties.test.ts index 751ebcf11205..fafccf209777 100644 --- a/packages/validator/test/unit/services/attestationDuties.test.ts +++ b/packages/validator/test/unit/services/attestationDuties.test.ts @@ -18,7 +18,7 @@ import {ZERO_HASH_HEX} from "../../utils/types.js"; vi.mock("../../../src/services/chainHeaderTracker.js"); -describe("AttestationDutiesService", function () { +describe("AttestationDutiesService", () => { const api = getApiClientStub(); let validatorStore: ValidatorStore; @@ -33,7 +33,7 @@ describe("AttestationDutiesService", function () { const defaultValidator: routes.beacon.ValidatorResponse = { index, balance: 32e9, - status: "active", + status: "active_ongoing", validator: ssz.phase0.Validator.defaultValue(), }; @@ -52,7 +52,7 @@ describe("AttestationDutiesService", function () { index, validator: {...defaultValidator.validator, pubkey: pubkeys[0]}, }; - api.beacon.getStateValidators.mockResolvedValue( + api.beacon.postStateValidators.mockResolvedValue( mockApiResponse({data: [validatorResponse], meta: {executionOptimistic: false, finalized: false}}) ); }); @@ -61,7 +61,7 @@ describe("AttestationDutiesService", function () { controller.abort(); }); - it("Should fetch indexes and duties", async function () { + it("Should fetch indexes and duties", async () => { // Reply with some duties const slot = 1; const epoch = computeEpochAtSlot(slot); @@ -118,7 +118,7 @@ describe("AttestationDutiesService", function () { expect(api.validator.prepareBeaconCommitteeSubnet).toHaveBeenCalledOnce(); }); - it("Should remove signer from attestation duties", async function () { + it("Should remove signer from attestation duties", async () => { // Reply with some duties const slot = 1; const duty: routes.validator.AttesterDuty = { @@ -165,7 +165,7 @@ describe("AttestationDutiesService", function () { expect(Object.fromEntries(dutiesService["dutiesByIndexByEpoch"])).toEqual({}); }); - it("Should fetch duties when node is resynced", async function () { + it("Should fetch duties when node is resynced", async () => { // Node is syncing api.node.getSyncingStatus.mockResolvedValue( mockApiResponse({data: {headSlot: 0, syncDistance: 1, isSyncing: true, isOptimistic: false, elOffline: false}}) diff --git a/packages/validator/test/unit/services/block.test.ts b/packages/validator/test/unit/services/block.test.ts index 641ca620a1b5..dc89f1169d92 100644 --- a/packages/validator/test/unit/services/block.test.ts +++ b/packages/validator/test/unit/services/block.test.ts @@ -16,7 +16,7 @@ import {ZERO_HASH_HEX} from "../../utils/types.js"; vi.mock("../../../src/services/validatorStore.js"); -describe("BlockDutiesService", function () { +describe("BlockDutiesService", () => { const api = getApiClientStub(); // @ts-expect-error - Mocked class don't need parameters const validatorStore = vi.mocked(new ValidatorStore()); @@ -36,7 +36,7 @@ describe("BlockDutiesService", function () { }); afterEach(() => controller.abort()); - it("Should produce, sign, and publish a block", async function () { + it("Should produce, sign, and publish a block", async () => { // Reply with some duties const slot = 0; // genesisTime is right now, so test with slot = currentSlot api.validator.getProposerDuties.mockResolvedValue( @@ -111,7 +111,7 @@ describe("BlockDutiesService", function () { ]); }); - it("Should produce, sign, and publish a blinded block", async function () { + it("Should produce, sign, and publish a blinded block", async () => { // Reply with some duties const slot = 0; // genesisTime is right now, so test with slot = currentSlot api.validator.getProposerDuties.mockResolvedValue( diff --git a/packages/validator/test/unit/services/blockDuties.test.ts b/packages/validator/test/unit/services/blockDuties.test.ts index bc4053b704c8..51ced2cd70de 100644 --- a/packages/validator/test/unit/services/blockDuties.test.ts +++ b/packages/validator/test/unit/services/blockDuties.test.ts @@ -13,7 +13,7 @@ import {ClockMock} from "../../utils/clock.js"; import {initValidatorStore} from "../../utils/validatorStore.js"; import {ZERO_HASH_HEX} from "../../utils/types.js"; -describe("BlockDutiesService", function () { +describe("BlockDutiesService", () => { const api = getApiClientStub(); let validatorStore: ValidatorStore; let pubkeys: Uint8Array[]; // Initialize pubkeys in before() so bls is already initialized @@ -30,7 +30,7 @@ describe("BlockDutiesService", function () { }); afterEach(() => controller.abort()); - it("Should fetch and persist block duties", async function () { + it("Should fetch and persist block duties", async () => { // Reply with some duties const slot = 0; // genesisTime is right now, so test with slot = currentSlot const duties: routes.validator.ProposerDutyList = [{slot: slot, validatorIndex: 0, pubkey: pubkeys[0]}]; @@ -106,7 +106,7 @@ describe("BlockDutiesService", function () { expect(notifyBlockProductionFn.mock.calls[1]).toEqual([1, [pubkeys[1]]]); }); - it("Should remove signer from duty", async function () { + it("Should remove signer from duty", async () => { // Reply with some duties const slot = 0; // genesisTime is right now, so test with slot = currentSlot const duties: routes.validator.ProposerDutyList = [ diff --git a/packages/validator/test/unit/services/doppelganger.test.ts b/packages/validator/test/unit/services/doppelganger.test.ts index 9b669b3c9396..943f0c08a9d3 100644 --- a/packages/validator/test/unit/services/doppelganger.test.ts +++ b/packages/validator/test/unit/services/doppelganger.test.ts @@ -213,8 +213,10 @@ function getMockBeaconApi(livenessMap: LivenessMap): ApiClient { } class ClockMockMsToSlot extends ClockMock { - constructor(public currentEpoch: Epoch) { + currentEpoch: Epoch; + constructor(currentEpoch: Epoch) { super(); + this.currentEpoch = currentEpoch; } async tickEpoch(epoch: Epoch, signal: AbortSignal): Promise { diff --git a/packages/validator/test/unit/services/indicesService.test.ts b/packages/validator/test/unit/services/indicesService.test.ts index 8332bac7cfd6..d4619aa65b94 100644 --- a/packages/validator/test/unit/services/indicesService.test.ts +++ b/packages/validator/test/unit/services/indicesService.test.ts @@ -6,7 +6,7 @@ import {getApiClientStub} from "../../utils/apiStub.js"; import {testLogger} from "../../utils/logger.js"; import {IndicesService} from "../../../src/services/indices.js"; -describe("IndicesService", function () { +describe("IndicesService", () => { const logger = testLogger(); const api = getApiClientStub(); @@ -20,7 +20,7 @@ describe("IndicesService", function () { pubkeys = secretKeys.map((sk) => sk.toPublicKey().toBytes()); }); - it("Should remove pubkey", async function () { + it("Should remove pubkey", async () => { const indicesService = new IndicesService(logger, api, null); const firstValidatorIndex = 0; const secondValidatorIndex = 1; diff --git a/packages/validator/test/unit/services/syncCommitteDuties.test.ts b/packages/validator/test/unit/services/syncCommitteDuties.test.ts index 10bac3ab2fe9..27a86d31f901 100644 --- a/packages/validator/test/unit/services/syncCommitteDuties.test.ts +++ b/packages/validator/test/unit/services/syncCommitteDuties.test.ts @@ -20,9 +20,7 @@ import {ClockMock} from "../../utils/clock.js"; import {initValidatorStore} from "../../utils/validatorStore.js"; import {syncCommitteeIndicesToSubnets} from "../../../src/services/utils.js"; -/* eslint-disable @typescript-eslint/naming-convention */ - -describe("SyncCommitteeDutiesService", function () { +describe("SyncCommitteeDutiesService", () => { const api = getApiClientStub(); let validatorStore: ValidatorStore; @@ -38,7 +36,7 @@ describe("SyncCommitteeDutiesService", function () { const defaultValidator: routes.beacon.ValidatorResponse = { index: indices[0], balance: 32e9, - status: "active", + status: "active_ongoing", validator: ssz.phase0.Validator.defaultValue(), }; @@ -60,7 +58,7 @@ describe("SyncCommitteeDutiesService", function () { index: indices[i], validator: {...defaultValidator.validator, pubkey: pubkeys[i]}, })); - api.beacon.getStateValidators.mockResolvedValue( + api.beacon.postStateValidators.mockResolvedValue( mockApiResponse({data: validatorResponses, meta: {executionOptimistic: false, finalized: false}}) ); }); @@ -69,7 +67,7 @@ describe("SyncCommitteeDutiesService", function () { controller.abort(); }); - it("Should fetch indexes and duties", async function () { + it("Should fetch indexes and duties", async () => { // Reply with some duties const slot = 1; const duty: routes.validator.SyncDuty = { @@ -129,7 +127,7 @@ describe("SyncCommitteeDutiesService", function () { /** * Reproduce https://github.com/ChainSafe/lodestar/issues/3572 */ - it("should remove redundant duties", async function () { + it("should remove redundant duties", async () => { // Reply with some duties const duty: routes.validator.SyncDuty = { pubkey: pubkeys[0], @@ -197,7 +195,7 @@ describe("SyncCommitteeDutiesService", function () { } as typeof dutiesByIndexByPeriodObj); }); - it("Should remove signer from sync committee duties", async function () { + it("Should remove signer from sync committee duties", async () => { // Reply with some duties const duty1: routes.validator.SyncDuty = { pubkey: pubkeys[0], @@ -210,7 +208,6 @@ describe("SyncCommitteeDutiesService", function () { validatorSyncCommitteeIndices: [7], }; when(api.validator.getSyncCommitteeDuties) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment .calledWith({epoch: expect.any(Number), indices}) .thenResolve(mockApiResponse({data: [duty1, duty2], meta: {executionOptimistic: false}})); @@ -267,7 +264,7 @@ describe("SyncCommitteeDutiesService", function () { } as typeof dutiesByIndexByPeriodObjAfterRemoval); }); - it("Should fetch duties when node is resynced", async function () { + it("Should fetch duties when node is resynced", async () => { // Node is syncing api.node.getSyncingStatus.mockResolvedValue( mockApiResponse({data: {headSlot: 0, syncDistance: 1, isSyncing: true, isOptimistic: false, elOffline: false}}) diff --git a/packages/validator/test/unit/services/syncCommittee.test.ts b/packages/validator/test/unit/services/syncCommittee.test.ts index 66e63ab72a6c..84c7f14d73b1 100644 --- a/packages/validator/test/unit/services/syncCommittee.test.ts +++ b/packages/validator/test/unit/services/syncCommittee.test.ts @@ -21,9 +21,7 @@ vi.mock("../../../src/services/emitter.js"); vi.mock("../../../src/services/chainHeaderTracker.js"); vi.mock("../../../src/services/syncingStatusTracker.js"); -/* eslint-disable @typescript-eslint/naming-convention */ - -describe("SyncCommitteeService", function () { +describe("SyncCommitteeService", () => { const api = getApiClientStub(); // @ts-expect-error - Mocked class don't need parameters const validatorStore = vi.mocked(new ValidatorStore()); @@ -102,7 +100,7 @@ describe("SyncCommitteeService", function () { ]; // Return empty replies to duties service - api.beacon.getStateValidators.mockResolvedValue( + api.beacon.postStateValidators.mockResolvedValue( mockApiResponse({data: [], meta: {executionOptimistic: false, finalized: false}}) ); api.validator.getSyncCommitteeDuties.mockResolvedValue( diff --git a/packages/validator/test/unit/services/syncingStatusTracker.test.ts b/packages/validator/test/unit/services/syncingStatusTracker.test.ts index 59029e1b9c51..07364847e345 100644 --- a/packages/validator/test/unit/services/syncingStatusTracker.test.ts +++ b/packages/validator/test/unit/services/syncingStatusTracker.test.ts @@ -4,7 +4,7 @@ import {getMockedLogger} from "../../utils/logger.js"; import {ClockMock} from "../../utils/clock.js"; import {SyncingStatus, SyncingStatusTracker} from "../../../src/services/syncingStatusTracker.js"; -describe("SyncingStatusTracker", function () { +describe("SyncingStatusTracker", () => { const api = getApiClientStub(); const logger = getMockedLogger(); @@ -26,7 +26,7 @@ describe("SyncingStatusTracker", function () { controller.abort(); }); - it("should handle transition from syncing to synced", async function () { + it("should handle transition from syncing to synced", async () => { // Node is syncing const syncing: SyncingStatus = { headSlot: 0, @@ -80,7 +80,7 @@ describe("SyncingStatusTracker", function () { expect(callOnResynced).toHaveBeenCalledOnce(); }); - it("should handle errors when checking syncing status", async function () { + it("should handle errors when checking syncing status", async () => { // Node is offline const error = new Error("ECONNREFUSED"); api.node.getSyncingStatus.mockRejectedValue(error); @@ -92,7 +92,7 @@ describe("SyncingStatusTracker", function () { expect(callOnResynced).not.toHaveBeenCalled(); }); - it("should not call scheduled tasks if already synced", async function () { + it("should not call scheduled tasks if already synced", async () => { // Node is already synced const syncedHead1: SyncingStatus = { headSlot: 1, diff --git a/packages/validator/test/unit/slashingProtection/interchange/index.test.ts b/packages/validator/test/unit/slashingProtection/interchange/index.test.ts index af2694458368..4038c6d1d4c5 100644 --- a/packages/validator/test/unit/slashingProtection/interchange/index.test.ts +++ b/packages/validator/test/unit/slashingProtection/interchange/index.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ import {describe, it, expect} from "vitest"; import {toHexString} from "@chainsafe/ssz"; import {Root, ssz} from "@lodestar/types"; diff --git a/packages/validator/test/unit/slashingProtection/minMaxSurround/utils.ts b/packages/validator/test/unit/slashingProtection/minMaxSurround/utils.ts index e57af53d6837..6d9e4e0f559d 100644 --- a/packages/validator/test/unit/slashingProtection/minMaxSurround/utils.ts +++ b/packages/validator/test/unit/slashingProtection/minMaxSurround/utils.ts @@ -8,11 +8,11 @@ export class DistanceMapStore { this.map = new Map(); } - async get(pubkey: BLSPubkey, epoch: number): Promise { + async get(_pubkey: BLSPubkey, epoch: number): Promise { return this.map.get(epoch) ?? null; } - async setBatch(pubkey: BLSPubkey, values: DistanceEntry[]): Promise { + async setBatch(_pubkey: BLSPubkey, values: DistanceEntry[]): Promise { for (const {source, distance} of values) { this.map.set(source, distance); } diff --git a/packages/validator/test/unit/utils/batch.test.ts b/packages/validator/test/unit/utils/batch.test.ts index 821d27c1a43a..455d31e19577 100644 --- a/packages/validator/test/unit/utils/batch.test.ts +++ b/packages/validator/test/unit/utils/batch.test.ts @@ -1,7 +1,7 @@ import {describe, it, expect} from "vitest"; import {batchItems} from "../../../src/util/index.js"; -describe("util / batch", function () { +describe("util / batch", () => { const testCases: {items: string[]; expected: string[][]}[] = [ {items: [], expected: []}, {items: ["1"], expected: [["1"]]}, diff --git a/packages/validator/test/unit/utils/clock.test.ts b/packages/validator/test/unit/utils/clock.test.ts index d668d753014a..056260d9e515 100644 --- a/packages/validator/test/unit/utils/clock.test.ts +++ b/packages/validator/test/unit/utils/clock.test.ts @@ -5,7 +5,7 @@ import {BeaconConfig} from "@lodestar/config"; import {Clock, getCurrentSlotAround} from "../../../src/util/clock.js"; import {testLogger} from "../../utils/logger.js"; -describe("util / Clock", function () { +describe("util / Clock", () => { const logger = testLogger(); let controller: AbortController; @@ -80,8 +80,7 @@ describe("util / Clock", function () { expect(onEpoch).toHaveBeenNthCalledWith(2, 1, expect.any(AbortSignal)); }); - describe("getCurrentSlot", function () { - // eslint-disable-next-line @typescript-eslint/naming-convention + describe("getCurrentSlot", () => { const testConfig = {SECONDS_PER_SLOT: 12} as BeaconConfig; const genesisTime = Math.floor(new Date("2021-01-01").getTime() / 1000); @@ -97,7 +96,7 @@ describe("util / Clock", function () { {name: "should return next slot after 12.5s", delta: 12.5}, ]; - it.each(testCase)("$name", async function ({delta}) { + it.each(testCase)("$name", async ({delta}) => { const currentSlot = getCurrentSlotAround(testConfig, genesisTime); vi.advanceTimersByTime(delta * 1000); expect(getCurrentSlotAround(testConfig, genesisTime)).toBe(currentSlot + 1); diff --git a/packages/validator/test/unit/utils/interopConfigs.ts b/packages/validator/test/unit/utils/interopConfigs.ts index d263fa8c1d8f..4805154b4b64 100644 --- a/packages/validator/test/unit/utils/interopConfigs.ts +++ b/packages/validator/test/unit/utils/interopConfigs.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - export const lighthouseHoleskyConfig = { CONFIG_NAME: "holesky", PRESET_BASE: "mainnet", @@ -125,7 +123,7 @@ export const lighthouseHoleskyConfig = { MAX_EFFECTIVE_BALANCE_ELECTRA: "2048000000000", MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: "65536", MIN_ACTIVATION_BALANCE: "32000000000", - PENDING_BALANCE_DEPOSITS_LIMIT: "134217728", + PENDING_DEPOSITS_LIMIT: "134217728", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "134217728", PENDING_CONSOLIDATIONS_LIMIT: "262144", MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "1", @@ -265,7 +263,7 @@ export const prysmHoleskyConfig = { WHISTLEBLOWER_REWARD_QUOTIENT: "512", MAX_EFFECTIVE_BALANCE_ELECTRA: "2048000000000", MIN_ACTIVATION_BALANCE: "32000000000", - PENDING_BALANCE_DEPOSITS_LIMIT: "134217728", + PENDING_DEPOSITS_LIMIT: "134217728", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "134217728", PENDING_CONSOLIDATIONS_LIMIT: "262144", MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "1", @@ -405,7 +403,7 @@ export const tekuHoleskyConfig = { MAX_EFFECTIVE_BALANCE_ELECTRA: "2048000000000", MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: "65536", MIN_ACTIVATION_BALANCE: "32000000000", - PENDING_BALANCE_DEPOSITS_LIMIT: "134217728", + PENDING_DEPOSITS_LIMIT: "134217728", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "134217728", PENDING_CONSOLIDATIONS_LIMIT: "262144", MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "1", @@ -548,7 +546,7 @@ export const nimbusHoleskyConfig = { MAX_EFFECTIVE_BALANCE_ELECTRA: "2048000000000", MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: "65536", MIN_ACTIVATION_BALANCE: "32000000000", - PENDING_BALANCE_DEPOSITS_LIMIT: "134217728", + PENDING_DEPOSITS_LIMIT: "134217728", PENDING_PARTIAL_WITHDRAWALS_LIMIT: "134217728", PENDING_CONSOLIDATIONS_LIMIT: "262144", MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: "1", diff --git a/packages/validator/test/unit/utils/params.test.ts b/packages/validator/test/unit/utils/params.test.ts index 79701e1f7a6e..13bab507cf13 100644 --- a/packages/validator/test/unit/utils/params.test.ts +++ b/packages/validator/test/unit/utils/params.test.ts @@ -12,8 +12,6 @@ const testCases: {name: string; items: [ChainConfig, Record]}[] {name: "nimbus", items: [networksChainConfig.holesky, nimbusHoleskyConfig]}, ]; -/* eslint-disable @typescript-eslint/naming-convention */ - describe("utils / params / assertEqualParams", () => { it("default == default", () => { const chainConfigJson = chainConfigToJson(chainConfig); diff --git a/packages/validator/test/unit/validatorStore.test.ts b/packages/validator/test/unit/validatorStore.test.ts index 3d8d88ee3e64..b029a33e163a 100644 --- a/packages/validator/test/unit/validatorStore.test.ts +++ b/packages/validator/test/unit/validatorStore.test.ts @@ -11,7 +11,7 @@ import {getApiClientStub} from "../utils/apiStub.js"; import {initValidatorStore} from "../utils/validatorStore.js"; import {ValidatorProposerConfig} from "../../src/services/validatorStore.js"; -describe("ValidatorStore", function () { +describe("ValidatorStore", () => { const api = getApiClientStub(); let validatorStore: ValidatorStore; @@ -48,7 +48,7 @@ describe("ValidatorStore", function () { vi.resetAllMocks(); }); - it("Should validate graffiti,feeRecipient etc. from valProposerConfig and ValidatorStore", async function () { + it("Should validate graffiti,feeRecipient etc. from valProposerConfig and ValidatorStore", async () => { //pubkeys[0] values expect(validatorStore.getGraffiti(toHexString(pubkeys[0]))).toBe( valProposerConfig.proposerConfig[toHexString(pubkeys[0])].graffiti diff --git a/packages/validator/test/utils/apiStub.ts b/packages/validator/test/utils/apiStub.ts index ef615af03369..4443dab5c4ac 100644 --- a/packages/validator/test/utils/apiStub.ts +++ b/packages/validator/test/utils/apiStub.ts @@ -16,6 +16,7 @@ export function getApiClientStub(): ApiClientStub { return { beacon: { getStateValidators: vi.fn(), + postStateValidators: vi.fn(), publishBlindedBlockV2: vi.fn(), publishBlockV2: vi.fn(), submitPoolSyncCommitteeSignatures: vi.fn(), diff --git a/packages/validator/test/utils/spec.ts b/packages/validator/test/utils/spec.ts index 91d6a75d1a52..ae0be94b4bfa 100644 --- a/packages/validator/test/utils/spec.ts +++ b/packages/validator/test/utils/spec.ts @@ -1,7 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -/* eslint-disable @typescript-eslint/naming-convention */ export type SlashingProtectionInterchangeTest = { name: string; genesis_validators_root: string; diff --git a/scripts/assert_eslintrc_sorted.mjs b/scripts/assert_eslintrc_sorted.mjs deleted file mode 100755 index 0b298b627340..000000000000 --- a/scripts/assert_eslintrc_sorted.mjs +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env node - -import assert from "node:assert"; -import eslintrc from "../.eslintrc.js"; - -assertSorted(eslintrc.extends, ".extends"); -assertSorted(Object.keys(eslintrc.rules), ".rules"); -for (const overrides of eslintrc.overrides) { - assertSorted(Object.keys(overrides.rules), `.overrides ${overrides.files.join(",")}`); -} - -/** @param {string[]} keys @param {string} id */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -function assertSorted(keys, id) { - try { - assert.deepStrictEqual(keys, [...keys].sort()); - } catch (e) { - // eslint-disable-next-line no-console - console.log(`Lint error in ${id}\n\n`, e.message); - process.exit(1); - } -} diff --git a/scripts/release/utils.mjs b/scripts/release/utils.mjs index ee02015f0577..19ae6f3d306b 100644 --- a/scripts/release/utils.mjs +++ b/scripts/release/utils.mjs @@ -50,7 +50,7 @@ export function parseCmdArgs() { // optional arg, defaults to HEAD try { commit = shell(`git log -n 1 --pretty='%h' ${commitArg ?? "HEAD"}`); - } catch (e) { + } catch (_e) { throw Error(`Invalid commit ${commitArg}`); } @@ -62,7 +62,7 @@ export function parseCmdArgs() { try { if (versionObj.includePrerelease) throw Error("Includes pre-release"); if (semver.clean(versionArg) !== versionMMP) throw Error("No clean major.minor.path version"); - } catch (e) { + } catch (_e) { throw Error(`Bad argv[2] semver version '${versionArg}': ${e.message}`); } @@ -82,7 +82,7 @@ export function assertCommitExistsInBranch(commit, branch) { try { // Also, ensure the branch exists first headCommit = shell(`git rev-parse refs/heads/${branch}`); - } catch (e) { + } catch (_e) { throw Error(`Branch ${branch} does not exist: ${e.message}`); } @@ -95,7 +95,7 @@ export function assertCommitExistsInBranch(commit, branch) { try { shell(`git merge-base --is-ancestor ${commit} ${headCommit}`); - } catch (e) { + } catch (_e) { throw Error(`Commit ${commit} does not belong to branch ${branch}`); } } @@ -129,7 +129,7 @@ export async function confirm(message) { export function checkBranchExistsLocal(branch) { try { return shell(`git show-ref refs/heads/${branch}`); - } catch (e) { + } catch (_e) { return null; } } @@ -156,7 +156,7 @@ export function checkBranchExistsRemote(branch) { // Return the first part of the first line return out.split(/\s+/)[0]; - } catch (e) { + } catch (_e) { return null; } } @@ -169,7 +169,7 @@ export function checkBranchExistsRemote(branch) { export function checkTagExistsLocal(tag) { try { return shell(`git show-ref refs/tags/${tag}`); - } catch (e) { + } catch (_e) { return null; } } @@ -195,7 +195,7 @@ export function checkTagExistsRemote(tag) { // Return the first part of the first line return out.split(/\s+/)[0]; - } catch (e) { + } catch (_e) { return null; } } @@ -224,7 +224,7 @@ export function readMainPackageJson() { let jsonStr; try { jsonStr = fs.readFileSync(packageJsonPath, "utf8"); - } catch (e) { + } catch (_e) { if (e.code === "ENOENT") { throw Error(`Must run script from repo root dir, package.json not found at ${packageJsonPath}`); } else { diff --git a/scripts/vitest/setupFiles/dotenv.ts b/scripts/vitest/setupFiles/dotenv.ts index 71364c7426bc..511cefd8eed4 100644 --- a/scripts/vitest/setupFiles/dotenv.ts +++ b/scripts/vitest/setupFiles/dotenv.ts @@ -1,8 +1,6 @@ import path from "node:path"; // It's a dev dependency -// eslint-disable-next-line import/no-extraneous-dependencies import {config} from "dotenv"; -// eslint-disable-next-line @typescript-eslint/naming-convention const __dirname = new URL(".", import.meta.url).pathname; config({path: path.join(__dirname, "../../../.env.test")}); diff --git a/types/vitest/index.d.ts b/types/vitest/index.d.ts index 387edcfa5279..297ed11e1904 100644 --- a/types/vitest/index.d.ts +++ b/types/vitest/index.d.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-extraneous-dependencies, @typescript-eslint/no-unused-vars import * as vitest from "vitest"; interface CustomMatchers { @@ -42,7 +41,6 @@ interface CustomAsymmetricMatchers extends CustomMatchers { } declare module "vitest" { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - interface Assertion extends CustomMatchers {} + interface Assertion extends CustomMatchers {} interface AsymmetricMatchersContaining extends CustomAsymmetricMatchers {} } diff --git a/yarn.lock b/yarn.lock index 60e41c276e53..846335c80676 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@aashutoshrathi/word-wrap@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" - integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== - "@actions/cache@^1.0.7": version "1.0.7" resolved "https://registry.npmjs.org/@actions/cache/-/cache-1.0.7.tgz" @@ -289,6 +284,60 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@biomejs/biome@^1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-1.9.3.tgz#5362fc390ac00c82e3698824490e3801d012c1b0" + integrity sha512-POjAPz0APAmX33WOQFGQrwLvlu7WLV4CFJMlB12b6ZSg+2q6fYu9kZwLCOA+x83zXfcPd1RpuWOKJW0GbBwLIQ== + optionalDependencies: + "@biomejs/cli-darwin-arm64" "1.9.3" + "@biomejs/cli-darwin-x64" "1.9.3" + "@biomejs/cli-linux-arm64" "1.9.3" + "@biomejs/cli-linux-arm64-musl" "1.9.3" + "@biomejs/cli-linux-x64" "1.9.3" + "@biomejs/cli-linux-x64-musl" "1.9.3" + "@biomejs/cli-win32-arm64" "1.9.3" + "@biomejs/cli-win32-x64" "1.9.3" + +"@biomejs/cli-darwin-arm64@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.3.tgz#3a981835a7a891589b356bbdb4e50157e494aa7d" + integrity sha512-QZzD2XrjJDUyIZK+aR2i5DDxCJfdwiYbUKu9GzkCUJpL78uSelAHAPy7m0GuPMVtF/Uo+OKv97W3P9nuWZangQ== + +"@biomejs/cli-darwin-x64@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.3.tgz#0e33284e5def9cbc17705b6a9acbc22b161accb1" + integrity sha512-vSCoIBJE0BN3SWDFuAY/tRavpUtNoqiceJ5PrU3xDfsLcm/U6N93JSM0M9OAiC/X7mPPfejtr6Yc9vSgWlEgVw== + +"@biomejs/cli-linux-arm64-musl@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.3.tgz#b68e2fe56381cbf71770b6c785215448c47595fd" + integrity sha512-VBzyhaqqqwP3bAkkBrhVq50i3Uj9+RWuj+pYmXrMDgjS5+SKYGE56BwNw4l8hR3SmYbLSbEo15GcV043CDSk+Q== + +"@biomejs/cli-linux-arm64@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.3.tgz#bb8186f000bd7366c3a1822a4a505e374905c462" + integrity sha512-vJkAimD2+sVviNTbaWOGqEBy31cW0ZB52KtpVIbkuma7PlfII3tsLhFa+cwbRAcRBkobBBhqZ06hXoZAN8NODQ== + +"@biomejs/cli-linux-x64-musl@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.3.tgz#01ccee0db2ca2ec9fb51fa69b2fc9e96434b5b32" + integrity sha512-TJmnOG2+NOGM72mlczEsNki9UT+XAsMFAOo8J0me/N47EJ/vkLXxf481evfHLlxMejTY6IN8SdRSiPVLv6AHlA== + +"@biomejs/cli-linux-x64@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.3.tgz#82d6fb824dd2c76142ab8625e202eb63a34e14f1" + integrity sha512-x220V4c+romd26Mu1ptU+EudMXVS4xmzKxPVb9mgnfYlN4Yx9vD5NZraSx/onJnd3Gh/y8iPUdU5CDZJKg9COA== + +"@biomejs/cli-win32-arm64@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.3.tgz#7fac607ade8e204eecae09e127713f000da0ccf2" + integrity sha512-lg/yZis2HdQGsycUvHWSzo9kOvnGgvtrYRgoCEwPBwwAL8/6crOp3+f47tPwI/LI1dZrhSji7PNsGKGHbwyAhw== + +"@biomejs/cli-win32-x64@1.9.3": + version "1.9.3" + resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.3.tgz#1cbc269dcd5f29b034cb7f5982353c1cc3629318" + integrity sha512-cQMy2zanBkVLpmmxXdK6YePzmZx0s5Z7KEnwmrW54rcXK3myCNbQa09SwGZ8i/8sLw0H9F3X7K4rxVNGU8/D4Q== + "@bundled-es-modules/cookie@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz#c3b82703969a61cf6a46e959a012b2c257f6b164" @@ -350,40 +399,40 @@ "@chainsafe/bls-keygen" "^0.4.0" bls-eth-wasm "^0.4.8" -"@chainsafe/blst-darwin-arm64@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@chainsafe/blst-darwin-arm64/-/blst-darwin-arm64-2.0.3.tgz#28ecbcdbaeaebb1cf07a4e8edeaf7b138bc96f22" - integrity sha512-7LGFMBXhB5eM8zLQgblm471NjqnOhI0dv+OohmgWBwjA3Ph4rxTd0CRorZMiqy770MbiLninnYSWiTbjXLY6eQ== +"@chainsafe/blst-darwin-arm64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@chainsafe/blst-darwin-arm64/-/blst-darwin-arm64-2.2.0.tgz#0ab9083805c308106c2f2107df1e6376d9190b1b" + integrity sha512-BOOy2KHbV028cioPWaAMqHdLRKd6/3XyEmUEcQC2E/SpyYLdNcaKiBUYIU4pT9CrWBbJJxX68UI+3vZVg0M8/w== -"@chainsafe/blst-darwin-x64@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@chainsafe/blst-darwin-x64/-/blst-darwin-x64-2.0.3.tgz#d50cfd591a5b1698202ee97242e0f28a8c76d2be" - integrity sha512-RRgMLuP8rmd84+ht35Rc/vMDsvK6NZgJlchqis1ve0/Na3550gqVgbVY7Y8YDuW8VpGAslII56rDZNqEl7tVmg== +"@chainsafe/blst-darwin-x64@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@chainsafe/blst-darwin-x64/-/blst-darwin-x64-2.2.0.tgz#231943a7736f3f89d35e03fec890b7809c98ff1a" + integrity sha512-jG64cwIdPT7u/haRrW26tWCpfMfHBQCfGY169mFQifCwO4VEwvaiVBPOh5olFis6LjpcmD+O0jpM8GqrnsmUHQ== -"@chainsafe/blst-linux-arm64-gnu@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@chainsafe/blst-linux-arm64-gnu/-/blst-linux-arm64-gnu-2.0.3.tgz#05d0028d757a00126628fa6d498f3c8925dda259" - integrity sha512-zkCTMuv3pnWR2xtfcazt7PlJqnltH9yOHSjFy1U7/izowU1KUSSptcvJi+26i2tI5NNWbHVNCb8CqKbRxOdNTQ== +"@chainsafe/blst-linux-arm64-gnu@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@chainsafe/blst-linux-arm64-gnu/-/blst-linux-arm64-gnu-2.2.0.tgz#721aeec63e8e02aba3358a0084c095403a5438fa" + integrity sha512-L8xV2uuLn8we76vdzfryS9ePdheuZrmY6yArGUFaF1Uzcwml6V1/VvyPl9/uooo/YfVRIrvF/D+lQfI2GFAnhw== -"@chainsafe/blst-linux-arm64-musl@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@chainsafe/blst-linux-arm64-musl/-/blst-linux-arm64-musl-2.0.3.tgz#c6d924fc346d0e50f77d44dfdfe08a9010eceae3" - integrity sha512-aQA9W7TpqoYjMc6WiLJ1rMdrU+vG7kjaOr1ZdCijlBOVer3OP5qdBD16GfjeqaxIQsmMHzHkBf/isczONZ9sjw== +"@chainsafe/blst-linux-arm64-musl@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@chainsafe/blst-linux-arm64-musl/-/blst-linux-arm64-musl-2.2.0.tgz#dbbabaab93156548c86e2b2b3a1d27160b715000" + integrity sha512-0Vn0luxLYVgC3lvWT1MapFHSAoz99PldqjhilXTGv0AcAk/X5LXPH2RC9Dp2KJGqthyUkpbk1j47jUBfBI+BIg== -"@chainsafe/blst-linux-x64-gnu@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@chainsafe/blst-linux-x64-gnu/-/blst-linux-x64-gnu-2.0.3.tgz#0edc16cab944f9f55529d742e157fcf85455a8ce" - integrity sha512-pZtZ9tVmmqInEqSF8+SJGtVynBw4pDOYNzUVSfpHcwBpyIl3TZzuewCQfh487FcJ52c2vXelpA8MucBuRDiDZg== +"@chainsafe/blst-linux-x64-gnu@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@chainsafe/blst-linux-x64-gnu/-/blst-linux-x64-gnu-2.2.0.tgz#9f8ab825621b75227c75bb75d369d3d42e91fa74" + integrity sha512-gEY/z2SDBA7kXtFEI9VNhWTJAIjx16jdeAyCaS2k4ACGurWZaWk+Ee4KniTsr4WieSqeuNTUr7Pdja0Sr4EKNQ== -"@chainsafe/blst-linux-x64-musl@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@chainsafe/blst-linux-x64-musl/-/blst-linux-x64-musl-2.0.3.tgz#be61d9f50fe35bb4c8692e90583b998701b0409c" - integrity sha512-w/X5+QjDbJTiqlXkiv0TXnuWdkfMRvoTFJcy3RyM7s1ra1kvoB6JRjJMirUJUoJNcMhzThZqjWPeVVpiCrqrVw== +"@chainsafe/blst-linux-x64-musl@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@chainsafe/blst-linux-x64-musl/-/blst-linux-x64-musl-2.2.0.tgz#11e99ac12b0f83cad68da56f4e9cfc4aa403a2e6" + integrity sha512-58GKtiUmtVSuerRzPEcMNQZpICPboBKFnL7+1Wo+PSuajkvbae7tEFrFTtWeMoKIPgOEsPMnk96LF+0yNgavUg== -"@chainsafe/blst-win32-x64-msvc@2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@chainsafe/blst-win32-x64-msvc/-/blst-win32-x64-msvc-2.0.3.tgz#e4666de90c9fb4f26667decc6bd3f49e8e5b2e46" - integrity sha512-yHy8cF+PTpuOYiBx962gGH2+c2DY38x2Th7HijlBBFbytW+D/nMka1DTuuyuOVIb/un2aEVf7pVBUgXTbQHEwQ== +"@chainsafe/blst-win32-x64-msvc@2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@chainsafe/blst-win32-x64-msvc/-/blst-win32-x64-msvc-2.2.0.tgz#f32b164721ff5edc279f6d6cd0fffde0ad2fe16c" + integrity sha512-UFrZshl4dfX5Uh2zeKXAZtrkQ+otczHMON2tsrapQNICWmfHZrzE6pKuBL+9QeGAbgflwpbz7+D5nQRDpiuHxQ== "@chainsafe/blst@^0.2.0": version "0.2.11" @@ -394,18 +443,18 @@ node-fetch "^2.6.1" node-gyp "^8.4.0" -"@chainsafe/blst@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@chainsafe/blst/-/blst-2.0.3.tgz#12ae2544cb04a8cade626a6d5263d08432bdda4a" - integrity sha512-VMyqXAwgNtiHWj1ksEnHFxD2pmw+0VqQLCeNFacE5xPfscPcj+EVjmexbYdUTae52SfVx1E0F83kk1xLY5GwPw== +"@chainsafe/blst@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@chainsafe/blst/-/blst-2.2.0.tgz#ced8b861b94934e3c1c53e173c3e1205d775d93b" + integrity sha512-VBaQoNE2a9d9+skAjQKv3Suk0yGKqp3mZM0YWYJNPj/Ae/f6lAyeVSgKqo2LrsNQBzD/LqrJLKUY8rJT3vDKLA== optionalDependencies: - "@chainsafe/blst-darwin-arm64" "2.0.3" - "@chainsafe/blst-darwin-x64" "2.0.3" - "@chainsafe/blst-linux-arm64-gnu" "2.0.3" - "@chainsafe/blst-linux-arm64-musl" "2.0.3" - "@chainsafe/blst-linux-x64-gnu" "2.0.3" - "@chainsafe/blst-linux-x64-musl" "2.0.3" - "@chainsafe/blst-win32-x64-msvc" "2.0.3" + "@chainsafe/blst-darwin-arm64" "2.2.0" + "@chainsafe/blst-darwin-x64" "2.2.0" + "@chainsafe/blst-linux-arm64-gnu" "2.2.0" + "@chainsafe/blst-linux-arm64-musl" "2.2.0" + "@chainsafe/blst-linux-x64-gnu" "2.2.0" + "@chainsafe/blst-linux-x64-musl" "2.2.0" + "@chainsafe/blst-win32-x64-msvc" "2.2.0" "@chainsafe/discv5@^9.0.0": version "9.0.0" @@ -438,19 +487,6 @@ uint8-varint "^2.0.2" uint8arrays "^5.0.1" -"@chainsafe/eslint-plugin-node@^11.2.3": - version "11.2.3" - resolved "https://registry.npmjs.org/@chainsafe/eslint-plugin-node/-/eslint-plugin-node-11.2.3.tgz" - integrity sha512-2iQv5JEaPcJuKP4pdawGd6iOVoUEDwx/PhsLqevwQXXz0WYwazW+p1fTF1bAcQNvZzLz4/wEBPtcVpQKNwY+jw== - dependencies: - eslint-plugin-es "^4.1.0" - eslint-utils "^2.0.0" - ignore "^5.1.1" - is-core-module "^2.3.0" - minimatch "^3.0.4" - resolve "^1.10.1" - semver "^6.1.0" - "@chainsafe/fast-crc32c@^4.1.1": version "4.1.1" resolved "https://registry.yarnpkg.com/@chainsafe/fast-crc32c/-/fast-crc32c-4.1.1.tgz#f551284ecf8325f676a1e26b938bcca51b9c8d93" @@ -586,6 +622,42 @@ resolved "https://registry.yarnpkg.com/@chainsafe/prometheus-gc-stats/-/prometheus-gc-stats-1.0.2.tgz#585f8f1555251db156d7e50ef8c86dd4f3e78f70" integrity sha512-h3mFKduSX85XMVbOdWOYvx9jNq99jGcRVNyW5goGOqju1CsI+ZJLhu5z4zBb/G+ksL0R4uLVulu/mIMe7Y0rNg== +"@chainsafe/pubkey-index-map-darwin-arm64@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/pubkey-index-map-darwin-arm64/-/pubkey-index-map-darwin-arm64-2.0.0.tgz#e468e772787f2411ecab8e8316da6c801356b72d" + integrity sha512-7eROFdQvwN1b0zJy0YJd1wBSv8j+Sp8tc3HsyaLQvjX7w93LcPPe+2Y5QpMkECBFzD2BcvKFpYxIvkDzV2v8rw== + +"@chainsafe/pubkey-index-map-darwin-x64@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/pubkey-index-map-darwin-x64/-/pubkey-index-map-darwin-x64-2.0.0.tgz#995755f71bcb49e5393a6af122c11a850aef4ce4" + integrity sha512-HfKIV83Y+AOugw0jaeUIHqe4Ikfwo47baFg97fpdcpUwPfWnw4Blej5C1zAyEX2IuUo2S1D450neTBSUgSdNCA== + +"@chainsafe/pubkey-index-map-linux-arm64-gnu@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/pubkey-index-map-linux-arm64-gnu/-/pubkey-index-map-linux-arm64-gnu-2.0.0.tgz#0c25ffb451d9861515e26e68006aa08e18ebc42d" + integrity sha512-t7Tdy+m9lZF2gqs0LmxFTAztNe6tDuSxje0xS8LTYanBSWQ6ADbWjTxcp/63yBbIYGzncigePZG2iis9nxB95Q== + +"@chainsafe/pubkey-index-map-linux-x64-gnu@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/pubkey-index-map-linux-x64-gnu/-/pubkey-index-map-linux-x64-gnu-2.0.0.tgz#26c3628faaeb1ef9b47952cc03ae209d7e9656d8" + integrity sha512-1DKoITe7ZwjClhCBpIZq7SOIOJbUNLxgsFuV7e0ZcBq+tz5UqhKB8SSRzNn7THoo+XRg1mJiDyFPzDKGxHxRkg== + +"@chainsafe/pubkey-index-map-win32-x64-msvc@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/pubkey-index-map-win32-x64-msvc/-/pubkey-index-map-win32-x64-msvc-2.0.0.tgz#0e35c67ed9dcaaee6ff9e582ed6a733d852473e9" + integrity sha512-hnEZBtTFxTl52lytogexOtzqPQyUKKB28mLbLTZnl2OicsEfNcczJpgF6o1uQ0O0zktAn/m1Tc6/iHmQg2VuhQ== + +"@chainsafe/pubkey-index-map@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@chainsafe/pubkey-index-map/-/pubkey-index-map-2.0.0.tgz#a8262b353e8335e9acf5e750353a53c55e5cf9be" + integrity sha512-2mVvWrHGApF3mPS7ecp8k3dI/C3QF5824bpQNSRWDsmZEU9H3HzITIj256v14QiB+22MIitpWkBc6hl2bjhJ+Q== + optionalDependencies: + "@chainsafe/pubkey-index-map-darwin-arm64" "2.0.0" + "@chainsafe/pubkey-index-map-darwin-x64" "2.0.0" + "@chainsafe/pubkey-index-map-linux-arm64-gnu" "2.0.0" + "@chainsafe/pubkey-index-map-linux-x64-gnu" "2.0.0" + "@chainsafe/pubkey-index-map-win32-x64-msvc" "2.0.0" + "@chainsafe/ssz@^0.11.1": version "0.11.1" resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.11.1.tgz#d4aec883af2ec5196ae67b96242c467da20b2476" @@ -594,14 +666,68 @@ "@chainsafe/as-sha256" "^0.4.1" "@chainsafe/persistent-merkle-tree" "^0.6.1" -"@chainsafe/ssz@^0.17.1": - version "0.17.1" - resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.17.1.tgz#7986afbcad5e6971006d596fdb7dfa34bc195131" - integrity sha512-1ay46QqYcVTBvUnDXTPTi5WTiENu7tIxpZGMDpUWps1/nYBmh/We/UoCF/jO+o/fkcDD3p8xQPlHbcCfy+jyjA== +"@chainsafe/ssz@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.18.0.tgz#773d40df9dff3b6a2a4c6685d9797abceb9d36f7" + integrity sha512-1ikTjk3JK6+fsGWiT5IvQU0AP6gF3fDzGmPfkKthbcbgTUR8fjB83Ywp9ko/ZoiDGfrSFkATgT4hvRzclu0IAA== dependencies: "@chainsafe/as-sha256" "0.5.0" "@chainsafe/persistent-merkle-tree" "0.8.0" +"@chainsafe/swap-or-not-shuffle-darwin-arm64@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@chainsafe/swap-or-not-shuffle-darwin-arm64/-/swap-or-not-shuffle-darwin-arm64-0.0.2.tgz#9c4b02970619b9ec5274f357f3a03ff97e745957" + integrity sha512-e2tmpSgGTHFv1g3oEKP/YElDTmxZwuSwVQ+Cf0gTUA8z4YQsJar61293My6JeA7qC5razWjhvcEk13ZbTCqk0w== + +"@chainsafe/swap-or-not-shuffle-darwin-x64@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@chainsafe/swap-or-not-shuffle-darwin-x64/-/swap-or-not-shuffle-darwin-x64-0.0.2.tgz#d2777b93455a217bf4b5b94b84e76239bc3ce860" + integrity sha512-JrK4psAQz0HzlfnsGRcHnhJSv6OHfjVNkEtERnpAjyeigwCRh09wNBzHEnIk8SOGFk29DabI4FC1wdh2Cs0DAA== + +"@chainsafe/swap-or-not-shuffle-linux-arm64-gnu@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@chainsafe/swap-or-not-shuffle-linux-arm64-gnu/-/swap-or-not-shuffle-linux-arm64-gnu-0.0.2.tgz#a881c6e29704bbbaceb8b75fd9394832087979f3" + integrity sha512-nGTIRUXt1QRNiWQXZnA8IWiHnAw6FNkU7RkngkoDzjD8pEhrtWs8tv/pdOxRKEhw21HBvKi7z8J+a7/MtxDLTg== + +"@chainsafe/swap-or-not-shuffle-linux-arm64-musl@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@chainsafe/swap-or-not-shuffle-linux-arm64-musl/-/swap-or-not-shuffle-linux-arm64-musl-0.0.2.tgz#118f80f7bd7e8f83fb566112a17003f228d86593" + integrity sha512-sumwkxQ0Mky+W66Jf43cHUybgHQ4FENj2iRBRw3jGWiZ79Vv/DZ1dMA6I4/LVWCsvZmFUIvMvKNthGHXefh2DQ== + +"@chainsafe/swap-or-not-shuffle-linux-x64-gnu@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@chainsafe/swap-or-not-shuffle-linux-x64-gnu/-/swap-or-not-shuffle-linux-x64-gnu-0.0.2.tgz#2dca41ba2848f904b5c9bc831cb0428c958261f3" + integrity sha512-do9NH/43eUWxtY+k3fxFltSSfKJpyvAINA/dJ3EHVkqS/oBTwR450CVNV5HFcR5Uvgxuth3/BjVYb4APs8N/MQ== + +"@chainsafe/swap-or-not-shuffle-linux-x64-musl@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@chainsafe/swap-or-not-shuffle-linux-x64-musl/-/swap-or-not-shuffle-linux-x64-musl-0.0.2.tgz#75025eee02e2b34f10852c1de57b8e0050584320" + integrity sha512-ClwDMZd768PIaAUQhbyZpqqip0b6Sgjt8IpP5ACYMJr2AHoiG64POZaiFu+zusT4q3s4/dvqaz86jRQaF4vFkw== + +"@chainsafe/swap-or-not-shuffle-win32-arm64-msvc@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@chainsafe/swap-or-not-shuffle-win32-arm64-msvc/-/swap-or-not-shuffle-win32-arm64-msvc-0.0.2.tgz#f5520df2bf72e823ef225d9f13d317b91b6372e4" + integrity sha512-i9+qj0VSppy2xMChQE38rCmWmmy8SJ0uSaApc0L4KZ+t2aqquYyEUXWGgfdXMZx9MqAUvuigzv34T9qivADFag== + +"@chainsafe/swap-or-not-shuffle-win32-x64-msvc@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@chainsafe/swap-or-not-shuffle-win32-x64-msvc/-/swap-or-not-shuffle-win32-x64-msvc-0.0.2.tgz#7413aa32a4006bf144b66087844aee71bb2a5f42" + integrity sha512-zZ9J0PWzGEgfjjAVMeTvAPdHNs5XCeQqfNQ0E/lQUYhS8Wi84y44R+7M6C/KIcS0GmvCpZXI2sQGeNjqbU5LoA== + +"@chainsafe/swap-or-not-shuffle@^0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@chainsafe/swap-or-not-shuffle/-/swap-or-not-shuffle-0.0.2.tgz#59a148b80bb8e8d4f2f38f7bd00562c7a6b99e62" + integrity sha512-Rbk3p86cX71VAq46+nRcBOHOetK2ls4QrAUf1YOWTYLHmmLvx8j9Q6gQndjtu0OeYGd7Eyj1pQCFDlS9kixk+g== + optionalDependencies: + "@chainsafe/swap-or-not-shuffle-darwin-arm64" "0.0.2" + "@chainsafe/swap-or-not-shuffle-darwin-x64" "0.0.2" + "@chainsafe/swap-or-not-shuffle-linux-arm64-gnu" "0.0.2" + "@chainsafe/swap-or-not-shuffle-linux-arm64-musl" "0.0.2" + "@chainsafe/swap-or-not-shuffle-linux-x64-gnu" "0.0.2" + "@chainsafe/swap-or-not-shuffle-linux-x64-musl" "0.0.2" + "@chainsafe/swap-or-not-shuffle-win32-arm64-msvc" "0.0.2" + "@chainsafe/swap-or-not-shuffle-win32-x64-msvc" "0.0.2" + "@chainsafe/threads@^1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@chainsafe/threads/-/threads-1.11.1.tgz#0b3b8c76f5875043ef6d47aeeb681dc80378f205" @@ -778,38 +904,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== -"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== - dependencies: - eslint-visitor-keys "^3.3.0" - -"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.1.tgz#8c4bb756cc2aa7eaf13cfa5e69c83afb3260c20c" - integrity sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ== - -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@eslint/js@8.57.0": - version "8.57.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" - integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== - "@ethereumjs/block@^4.2.2": version "4.2.2" resolved "https://registry.yarnpkg.com/@ethereumjs/block/-/block-4.2.2.tgz#fddecd34ed559f84ab8eb13098a6dee51a1360ae" @@ -1285,133 +1379,110 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" -"@fastify/accept-negotiator@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz#c1c66b3b771c09742a54dd5bc87c582f6b0630ff" - integrity sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ== +"@fastify/accept-negotiator@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@fastify/accept-negotiator/-/accept-negotiator-2.0.0.tgz#efce76b4d658e7ee669e681c2d79bffc9a654fdb" + integrity sha512-/Sce/kBzuTxIq5tJh85nVNOq9wKD8s+viIgX0fFMDBdw95gnpf53qmF1oBgJym3cPFliWUuSloVg/1w/rH0FcQ== -"@fastify/ajv-compiler@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz#459bff00fefbf86c96ec30e62e933d2379e46670" - integrity sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA== +"@fastify/ajv-compiler@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@fastify/ajv-compiler/-/ajv-compiler-4.0.1.tgz#9567b4c09149a0f342e931c7196a8ed9dc292954" + integrity sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw== dependencies: - ajv "^8.11.0" - ajv-formats "^2.1.1" - fast-uri "^2.0.0" + ajv "^8.12.0" + ajv-formats "^3.0.1" + fast-uri "^3.0.0" -"@fastify/bearer-auth@^9.0.0": - version "9.0.0" - resolved "https://registry.yarnpkg.com/@fastify/bearer-auth/-/bearer-auth-9.0.0.tgz#9a75abcb1d54751dc04e97b37db5363e325c0f90" - integrity sha512-I1egwg1LRdIvhjL/P+3UEfyK7A3YTnN3goTyf8MJ+v7vVkwyyd8ieccFKI0SzEuxMmfQh/p4yNyLZWcMVwpInA== +"@fastify/bearer-auth@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@fastify/bearer-auth/-/bearer-auth-10.0.1.tgz#893466052fa566c24eb4f44a3c6aacb50db96ecd" + integrity sha512-i2snRkAJsMmfFcsRS/fFIovcLL3WeZtxJP9pprx2NvB8N/l+fjMNmKeWWyX0hDS2Q0zEPqLz/G0DK92nqJYAJQ== dependencies: - fastify-plugin "^4.0.0" + "@fastify/error" "^4.0.0" + fastify-plugin "^5.0.0" "@fastify/busboy@^2.0.0": version "2.1.1" resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== -"@fastify/cors@^8.2.1": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@fastify/cors/-/cors-8.2.1.tgz#dd348162bcbfb87dff4b492e2bef32d41244006a" - integrity sha512-2H2MrDD3ea7g707g1CNNLWb9/tYbmw7HS+MK2SDcgjxwzbOFR93JortelTIO8DBFsZqFtEpKNxiZfSyrGgYcbw== +"@fastify/cors@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@fastify/cors/-/cors-10.0.1.tgz#c208fa5f672db31a8383400349e9852762903d64" + integrity sha512-O8JIf6448uQbOgzSkCqhClw6gFTAqrdfeA6R3fc/3gwTJGUp7gl8/3tbNB+6INuu4RmgVOq99BmvdGbtu5pgOA== dependencies: - fastify-plugin "^4.0.0" - mnemonist "0.39.5" - -"@fastify/deepmerge@^1.0.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@fastify/deepmerge/-/deepmerge-1.3.0.tgz#8116858108f0c7d9fd460d05a7d637a13fe3239a" - integrity sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A== + fastify-plugin "^5.0.0" + mnemonist "0.39.8" -"@fastify/error@^3.3.0", "@fastify/error@^3.4.0": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@fastify/error/-/error-3.4.1.tgz#b14bb4cac3dd4ec614becbc643d1511331a6425c" - integrity sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ== +"@fastify/error@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@fastify/error/-/error-4.0.0.tgz#7842d6161fbce78953638318be99033a0c2d5070" + integrity sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA== -"@fastify/fast-json-stringify-compiler@^4.3.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz#5df89fa4d1592cbb8780f78998355feb471646d5" - integrity sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA== +"@fastify/fast-json-stringify-compiler@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.1.tgz#659c74f3181fb4f984fe27dcc95d14366ae85ca0" + integrity sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA== dependencies: - fast-json-stringify "^5.7.0" + fast-json-stringify "^6.0.0" -"@fastify/merge-json-schemas@^0.1.0": +"@fastify/merge-json-schemas@^0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz#3551857b8a17a24e8c799e9f51795edb07baa0bc" integrity sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA== dependencies: fast-deep-equal "^3.1.3" -"@fastify/send@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@fastify/send/-/send-2.1.0.tgz#1aa269ccb4b0940a2dadd1f844443b15d8224ea0" - integrity sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA== +"@fastify/send@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@fastify/send/-/send-3.1.1.tgz#455a8fa56ae005c4c387ddf111364f346b848117" + integrity sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA== dependencies: - "@lukeed/ms" "^2.0.1" + "@lukeed/ms" "^2.0.2" escape-html "~1.0.3" fast-decode-uri-component "^1.0.1" - http-errors "2.0.0" - mime "^3.0.0" - -"@fastify/static@^6.0.0": - version "6.11.2" - resolved "https://registry.yarnpkg.com/@fastify/static/-/static-6.11.2.tgz#1fe40c40daf055a28d29db807b459fcff431d9b6" - integrity sha512-EH7mh7q4MfNdT7N07ZVlwsX/ObngMvQ7KBP0FXAuPov99Fjn80KSJMdxQhhYKAKWW1jXiFdrk8X7d6uGWdZFxg== - dependencies: - "@fastify/accept-negotiator" "^1.0.0" - "@fastify/send" "^2.0.0" - content-disposition "^0.5.3" - fastify-plugin "^4.0.0" - glob "^8.0.1" - p-limit "^3.1.0" + http-errors "^2.0.0" + mime "^3" -"@fastify/swagger-ui@^1.9.3": - version "1.9.3" - resolved "https://registry.yarnpkg.com/@fastify/swagger-ui/-/swagger-ui-1.9.3.tgz#1ec03ea2595cb2e7d6de6ae7c949bebcff8370a5" - integrity sha512-YYqce4CydjDIEry6Zo4JLjVPe5rjS8iGnk3fHiIQnth9sFSLeyG0U1DCH+IyYmLddNDg1uWJOuErlVqnu/jI3w== +"@fastify/static@^8.0.0": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@fastify/static/-/static-8.0.1.tgz#137059a4625c64cce8ee7eb513961c5e23018805" + integrity sha512-7idyhbcgf14v4bjWzUeHEFvnVxvNJ1n5cyGPgFtwTZjnjUQ1wgC7a2FQai7OGKqCKywDEjzbPhAZRW+uEK1LMg== dependencies: - "@fastify/static" "^6.0.0" - fastify-plugin "^4.0.0" - openapi-types "^12.0.2" - rfdc "^1.3.0" - yaml "^2.2.2" + "@fastify/accept-negotiator" "^2.0.0" + "@fastify/send" "^3.1.0" + content-disposition "^0.5.4" + fastify-plugin "^5.0.0" + fastq "^1.17.1" + glob "^11.0.0" + +"@fastify/swagger-ui@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@fastify/swagger-ui/-/swagger-ui-5.0.1.tgz#76c348bbaf7e49e3cfb62ebe3cc3fb15ef0eefb3" + integrity sha512-nCDV5l0OTziK8nIeHaLZ30ENFFftZ4Pcs7GHDcqOO6Jp3qSnyOsqBg1/EosM+d1mrCvH4vSlM09xolkjrbuJQQ== + dependencies: + "@fastify/static" "^8.0.0" + fastify-plugin "^5.0.0" + openapi-types "^12.1.3" + rfdc "^1.3.1" + yaml "^2.4.1" -"@fastify/swagger@^8.10.0": - version "8.10.0" - resolved "https://registry.yarnpkg.com/@fastify/swagger/-/swagger-8.10.0.tgz#d978ae9f2d802ab652955d02be7a125f7f6d9f05" - integrity sha512-0o6nd0qWpJbVSv/vbK4bzHSYe7l+PTGPqrQVwWIXVGd7CvXr585SBx+h8EgrMOY80bcOnGreqnjYFOV0osGP5A== +"@fastify/swagger@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@fastify/swagger/-/swagger-9.0.0.tgz#a6013ee3cf4ec0f2562e1455face9eb6ef787d89" + integrity sha512-E7TQbBCbhvS2djGLxJ7t2OFbhc2F+KCsOZCNhh6xQIlJxq9H4ZR5KuLKG+vn6COVqkLxRVUOZ9qtbbzdf5Jfqw== dependencies: - fastify-plugin "^4.0.0" + fastify-plugin "^5.0.0" json-schema-resolver "^2.0.0" - openapi-types "^12.0.0" - rfdc "^1.3.0" - yaml "^2.2.2" + openapi-types "^12.1.3" + rfdc "^1.3.1" + yaml "^2.4.2" "@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@humanwhocodes/config-array@^0.11.14": - version "0.11.14" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" - integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== - dependencies: - "@humanwhocodes/object-schema" "^2.0.2" - debug "^4.3.1" - minimatch "^3.0.5" - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" - integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== - "@hutson/parse-repository-url@^3.0.0": version "3.0.2" resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" @@ -1857,10 +1928,10 @@ race-signal "^1.0.2" uint8arraylist "^2.4.8" -"@lukeed/ms@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@lukeed/ms/-/ms-2.0.1.tgz#3c2bbc258affd9cc0e0cc7828477383c73afa6ee" - integrity sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA== +"@lukeed/ms@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@lukeed/ms/-/ms-2.0.2.tgz#07f09e59a74c52f4d88c6db5c1054e819538e2a8" + integrity sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA== "@microsoft/api-extractor-model@7.28.13": version "7.28.13" @@ -2170,7 +2241,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": +"@nodelib/fs.walk@^1.2.3": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -2625,11 +2696,6 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@pkgr/core@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" - integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== - "@polka/url@^1.0.0-next.24": version "1.0.0-next.24" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.24.tgz#58601079e11784d20f82d0585865bb42305c4df3" @@ -2681,85 +2747,85 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@rollup/rollup-android-arm-eabi@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.1.tgz#ad76cc870b1e2bc4476dfc02b82e20cea272a09d" - integrity sha512-92/y0TqNLRYOTXpm6Z7mnpvKAG9P7qmK7yJeRJSdzElNCUnsgbpAsGqerUboYRIQKzgfq4pWu9xVkgpWLfmNsw== - -"@rollup/rollup-android-arm64@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.16.1.tgz#e7bd4f2b8ec5e049f98edbc68d72cb05356f81d8" - integrity sha512-ttWB6ZCfRLuDIUiE0yiu5gcqOsYjA5F7kEV1ggHMj20FwLZ8A1FMeahZJFl/pnOmcnD2QL0z4AcDuo27utGU8A== - -"@rollup/rollup-darwin-arm64@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.16.1.tgz#8fd277b4be6cc956167710e36b4ee365f8a44050" - integrity sha512-QLDvPLetbqjHojTGFw9+nuSP3YY/iz2k1cep6crYlr97sS+ZJ0W43b8Z0zC00+lnFZj6JSNxiA4DjboNQMuh1A== - -"@rollup/rollup-darwin-x64@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.16.1.tgz#5ab829322926fefce42db3529649a1098b420fe3" - integrity sha512-TAUK/D8khRrRIa1KwRzo8JNKk3tcqaeXWdtsiLgA8zmACWwlWLjPCJ4DULGHQrMkeBjp1Cd3Yuwx04lZgFx5Vg== - -"@rollup/rollup-linux-arm-gnueabihf@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.16.1.tgz#0154bc34e6a88fb0147adc827512add8d3a2338c" - integrity sha512-KO+WGZjrh6zyFTD1alIFkfdtxf8B4BC+hqd3kBZHscPLvE5FR/6QKsyuCT0JlERxxYBSUKNUQ/UHyX5uwO1x2A== - -"@rollup/rollup-linux-arm-musleabihf@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.16.1.tgz#0f3fa433a81b389042555133d38b4b886b369e58" - integrity sha512-NqxbllzIB1WoAo4ThUXVtd21iiM5IHMTTXmXySKBLVcZvkU0HIZmatlP7hLzb5yQubcmdIeWmncd2NdsjocEiw== - -"@rollup/rollup-linux-arm64-gnu@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.16.1.tgz#c8f2d523ac4bcff382601306989b27137d536dd6" - integrity sha512-snma5NvV8y7IECQ5rq0sr0f3UUu+92NVmG/913JXJMcXo84h9ak9TA5UI9Cl2XRM9j3m37QwDBtEYnJzRkSmxA== - -"@rollup/rollup-linux-arm64-musl@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.16.1.tgz#86b5104635131182b6b2b6997c4aa5594ce557b7" - integrity sha512-KOvqGprlD84ueivhCi2flvcUwDRD20mAsE3vxQNVEI2Di9tnPGAfEu6UcrSPZbM+jG2w1oSr43hrPo0RNg6GGg== - -"@rollup/rollup-linux-powerpc64le-gnu@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.16.1.tgz#5de8b20105aaaeb36eb86fab0a1020d81c7bd4d5" - integrity sha512-/gsNwtiGLqYwN4vP+EIdUC6Q6LTlpupWqokqIndvZcjn9ig/5P01WyaYCU2wvfL/2Z82jp5kX8c1mDBOvCP3zg== - -"@rollup/rollup-linux-riscv64-gnu@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.16.1.tgz#5319629dcdcb85ba201c6f0f894c9472e7d1013d" - integrity sha512-uU8zuGkQfGqfD9w6VRJZI4IuG4JIfNxxJgEmLMAmPVHREKGsxFVfgHy5c6CexQF2vOfgjB33OsET3Vdn2lln9A== - -"@rollup/rollup-linux-s390x-gnu@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.16.1.tgz#246ac211ed0d78f7a9bc5c1d0653bde4c6cd9f63" - integrity sha512-lsjLtDgtcGFEuBP6yrXwkRN5/wKlvUZtfbKZZu0yaoNpiBL4epgnO21osAALIspVRnl4qZgyLFd8xjCYYWgwfw== - -"@rollup/rollup-linux-x64-gnu@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.16.1.tgz#d0c03203ddeb9454fc6fdde93a39b01c176ac6d9" - integrity sha512-N2ZizKhUryqqrMfdCnjhJhZRgv61C6gK+hwVtCIKC8ts8J+go+vqENnGexwg21nHIOvLN5mBM8a7DI2vlyIOPg== - -"@rollup/rollup-linux-x64-musl@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.16.1.tgz#20235632e2be4689d663aadaceaaf90df03b1a33" - integrity sha512-5ICeMxqg66FrOA2AbnBQ2TJVxfvZsKLxmof0ibvPLaYtbsJqnTUtJOofgWb46Gjd4uZcA4rdsp4JCxegzQPqCg== - -"@rollup/rollup-win32-arm64-msvc@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.16.1.tgz#af113ad682fc13d1f870242c5539031f8cc27cf1" - integrity sha512-1vIP6Ce02L+qWD7uZYRiFiuAJo3m9kARatWmFSnss0gZnVj2Id7OPUU9gm49JPGasgcR3xMqiH3fqBJ8t00yVg== - -"@rollup/rollup-win32-ia32-msvc@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.16.1.tgz#4e7b57e757c95da8e79092056d1b428617515668" - integrity sha512-Y3M92DcVsT6LoP+wrKpoUWPaazaP1fzbNkp0a0ZSj5Y//+pQVfVe/tQdsYQQy7dwXR30ZfALUIc9PCh9Izir6w== - -"@rollup/rollup-win32-x64-msvc@4.16.1": - version "4.16.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.16.1.tgz#5068a893ba292279adbe76fc487316724b15d811" - integrity sha512-x0fvpHMuF7fK5r8oZxSi8VYXkrVmRgubXpO/wcf15Lk3xZ4Jvvh5oG+u7Su1776A7XzVKZhD2eRc4t7H50gL3w== +"@rollup/rollup-android-arm-eabi@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz#8b613b9725e8f9479d142970b106b6ae878610d5" + integrity sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w== + +"@rollup/rollup-android-arm64@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz#654ca1049189132ff602bfcf8df14c18da1f15fb" + integrity sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA== + +"@rollup/rollup-darwin-arm64@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz#6d241d099d1518ef0c2205d96b3fa52e0fe1954b" + integrity sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q== + +"@rollup/rollup-darwin-x64@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz#42bd19d292a57ee11734c980c4650de26b457791" + integrity sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw== + +"@rollup/rollup-linux-arm-gnueabihf@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz#f23555ee3d8fe941c5c5fd458cd22b65eb1c2232" + integrity sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ== + +"@rollup/rollup-linux-arm-musleabihf@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz#f3bbd1ae2420f5539d40ac1fde2b38da67779baa" + integrity sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg== + +"@rollup/rollup-linux-arm64-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz#7abe900120113e08a1f90afb84c7c28774054d15" + integrity sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw== + +"@rollup/rollup-linux-arm64-musl@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz#9e655285c8175cd44f57d6a1e8e5dedfbba1d820" + integrity sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA== + +"@rollup/rollup-linux-powerpc64le-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz#9a79ae6c9e9d8fe83d49e2712ecf4302db5bef5e" + integrity sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg== + +"@rollup/rollup-linux-riscv64-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz#67ac70eca4ace8e2942fabca95164e8874ab8128" + integrity sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA== + +"@rollup/rollup-linux-s390x-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz#9f883a7440f51a22ed7f99e1d070bd84ea5005fc" + integrity sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q== + +"@rollup/rollup-linux-x64-gnu@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz#70116ae6c577fe367f58559e2cffb5641a1dd9d0" + integrity sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg== + +"@rollup/rollup-linux-x64-musl@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz#f473f88219feb07b0b98b53a7923be716d1d182f" + integrity sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g== + +"@rollup/rollup-win32-arm64-msvc@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz#4349482d17f5d1c58604d1c8900540d676f420e0" + integrity sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw== + +"@rollup/rollup-win32-ia32-msvc@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz#a6fc39a15db618040ec3c2a24c1e26cb5f4d7422" + integrity sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g== + +"@rollup/rollup-win32-x64-msvc@4.22.4": + version "4.22.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz#3dd5d53e900df2a40841882c02e56f866c04d202" + integrity sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q== "@rushstack/node-core-library@4.0.2": version "4.0.2" @@ -3147,21 +3213,6 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== -"@types/json-schema@^7.0.12": - version "7.0.13" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" - integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== - -"@types/json-schema@^7.0.15": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" - integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= - "@types/keyv@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" @@ -3235,11 +3286,11 @@ undici-types "~5.26.4" "@types/node@^20.12.8": - version "20.12.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.8.tgz#35897bf2bfe3469847ab04634636de09552e8256" - integrity sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w== + version "20.16.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.7.tgz#0a245bf5805add998a22b8b5adac612ee70190bc" + integrity sha512-QkDQjAY3gkvJNcZOWwzy3BN34RweT0OQ9zJyvLCU0kSK22dO2QYh/NHGfbEAYylPYzRB1/iXcojS79wOg5gFSw== dependencies: - undici-types "~5.26.4" + undici-types "~6.19.2" "@types/node@^20.14.11": version "20.14.11" @@ -3285,16 +3336,6 @@ resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz" integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== -"@types/semver@^7.5.0": - version "7.5.2" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564" - integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== - -"@types/semver@^7.5.8": - version "7.5.8" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" - integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== - "@types/sinon@^17.0.3": version "17.0.3" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.3.tgz#9aa7e62f0a323b9ead177ed23a36ea757141a5fa" @@ -3410,145 +3451,6 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz#5a5fcad1a7baed85c10080d71ad901f98c38d5b7" - integrity sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw== - dependencies: - "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "7.2.0" - "@typescript-eslint/type-utils" "7.2.0" - "@typescript-eslint/utils" "7.2.0" - "@typescript-eslint/visitor-keys" "7.2.0" - debug "^4.3.4" - graphemer "^1.4.0" - ignore "^5.2.4" - natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/parser@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.2.0.tgz#44356312aea8852a3a82deebdacd52ba614ec07a" - integrity sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg== - dependencies: - "@typescript-eslint/scope-manager" "7.2.0" - "@typescript-eslint/types" "7.2.0" - "@typescript-eslint/typescript-estree" "7.2.0" - "@typescript-eslint/visitor-keys" "7.2.0" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz#cfb437b09a84f95a0930a76b066e89e35d94e3da" - integrity sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg== - dependencies: - "@typescript-eslint/types" "7.2.0" - "@typescript-eslint/visitor-keys" "7.2.0" - -"@typescript-eslint/scope-manager@7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz#3f0db079b275bb8b0cb5be7613fb3130cfb5de77" - integrity sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw== - dependencies: - "@typescript-eslint/types" "7.7.0" - "@typescript-eslint/visitor-keys" "7.7.0" - -"@typescript-eslint/type-utils@7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz#7be5c30e9b4d49971b79095a1181324ef6089a19" - integrity sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA== - dependencies: - "@typescript-eslint/typescript-estree" "7.2.0" - "@typescript-eslint/utils" "7.2.0" - debug "^4.3.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/types@7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.2.0.tgz#0feb685f16de320e8520f13cca30779c8b7c403f" - integrity sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA== - -"@typescript-eslint/types@7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.7.0.tgz#23af4d24bf9ce15d8d301236e3e3014143604f27" - integrity sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w== - -"@typescript-eslint/typescript-estree@7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz#5beda2876c4137f8440c5a84b4f0370828682556" - integrity sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA== - dependencies: - "@typescript-eslint/types" "7.2.0" - "@typescript-eslint/visitor-keys" "7.2.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - minimatch "9.0.3" - semver "^7.5.4" - ts-api-utils "^1.0.1" - -"@typescript-eslint/typescript-estree@7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz#b5dd6383b4c6a852d7b256a37af971e8982be97f" - integrity sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ== - dependencies: - "@typescript-eslint/types" "7.7.0" - "@typescript-eslint/visitor-keys" "7.7.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^1.3.0" - -"@typescript-eslint/utils@7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.2.0.tgz#fc8164be2f2a7068debb4556881acddbf0b7ce2a" - integrity sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.12" - "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "7.2.0" - "@typescript-eslint/types" "7.2.0" - "@typescript-eslint/typescript-estree" "7.2.0" - semver "^7.5.4" - -"@typescript-eslint/utils@^7.1.1": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.7.0.tgz#3d2b6606a60ac34f3c625facfb3b3ab7e126f58d" - integrity sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.15" - "@types/semver" "^7.5.8" - "@typescript-eslint/scope-manager" "7.7.0" - "@typescript-eslint/types" "7.7.0" - "@typescript-eslint/typescript-estree" "7.7.0" - semver "^7.6.0" - -"@typescript-eslint/visitor-keys@7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz#5035f177752538a5750cca1af6044b633610bf9e" - integrity sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A== - dependencies: - "@typescript-eslint/types" "7.2.0" - eslint-visitor-keys "^3.4.1" - -"@typescript-eslint/visitor-keys@7.7.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz#950148cf1ac11562a2d903fdf7acf76714a2dc9e" - integrity sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA== - dependencies: - "@typescript-eslint/types" "7.7.0" - eslint-visitor-keys "^3.4.3" - -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== - "@vitest/browser@^2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@vitest/browser/-/browser-2.0.4.tgz#6569258b4a8085f348007acd5ecf61db4eec4340" @@ -3821,11 +3723,6 @@ abstract-logging@^2.0.1: resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" @@ -3836,11 +3733,6 @@ acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== -acorn@^8.9.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - add-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" @@ -3887,24 +3779,14 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== dependencies: ajv "^8.0.0" -ajv@^6.12.4, ajv@~6.12.6: - version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.10.0, ajv@^8.11.0, ajv@^8.12.0: +ajv@^8.0.0, ajv@^8.12.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -3924,6 +3806,16 @@ ajv@^8.6.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@~6.12.6: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ansi-colors@4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" @@ -4060,11 +3952,6 @@ archiver@^7.0.0: tar-stream "^3.0.0" zip-stream "^6.0.1" -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz" - integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= - are-we-there-yet@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz" @@ -4115,64 +4002,11 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== -array-includes@^3.1.7: - version "3.1.7" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" - integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - is-string "^1.0.7" - array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.filter@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz#423771edeb417ff5914111fff4277ea0624c0d0e" - integrity sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.7" - -array.prototype.findlastindex@^1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz#d1c50f0b3a9da191981ff8942a0aedd82794404f" - integrity sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.3.0" - es-shim-unscopables "^1.0.2" - -array.prototype.flat@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" - integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - -array.prototype.flatmap@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" - integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - arraybuffer.prototype.slice@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" @@ -4287,14 +4121,12 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -avvio@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/avvio/-/avvio-8.3.0.tgz#1e019433d935730b814978a583eefac41a65082f" - integrity sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q== +avvio@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/avvio/-/avvio-9.0.0.tgz#3ae02fb318377006e0e06a3f47842c98d8668607" + integrity sha512-UbYrOXgE/I+knFG+3kJr9AgC7uNo8DG+FGGODpH9Bj1O1kL/QDjBXnTem9leD3VdQKtaHjV3O85DQ7hHh4IIHw== dependencies: - "@fastify/error" "^3.3.0" - archy "^1.0.0" - debug "^4.0.0" + "@fastify/error" "^4.0.0" fastq "^1.17.1" aws-sdk@^2.932.0: @@ -4506,7 +4338,14 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -5229,7 +5068,7 @@ constants-browserify@^1.0.0: resolved "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@^0.5.3: +content-disposition@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== @@ -5434,7 +5273,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -5551,20 +5390,13 @@ de-indent@^1.0.2: resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - debug@^4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" @@ -5617,11 +5449,6 @@ deep-eql@^5.0.1: resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== -deep-is@^0.1.3: - version "0.1.3" - resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - deepmerge-ts@^5.0.0, deepmerge-ts@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz#c55206cc4c7be2ded89b9c816cf3608884525d7a" @@ -5658,7 +5485,7 @@ define-lazy-prop@^2.0.0: resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== -define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -5833,20 +5660,6 @@ dockerode@^3.3.5: docker-modem "^3.0.0" tar-fs "~2.0.1" -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - dom-accessibility-api@^0.5.9: version "0.5.16" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" @@ -5979,14 +5792,6 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -enhanced-resolve@^5.12.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - enquirer@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -6026,7 +5831,7 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.18.0-next.2, es-abstract@^1.22.1, es-abstract@^1.22.3: +es-abstract@^1.18.0-next.2, es-abstract@^1.22.1: version "1.22.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== @@ -6071,16 +5876,6 @@ es-abstract@^1.18.0-next.2, es-abstract@^1.22.1, es-abstract@^1.22.3: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== - -es-errors@^1.0.0, es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - es-set-tostringtag@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" @@ -6090,20 +5885,6 @@ es-set-tostringtag@^2.0.1: has "^1.0.3" has-tostringtag "^1.0.0" -es-shim-unscopables@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" - integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== - dependencies: - has "^1.0.3" - -es-shim-unscopables@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" - integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== - dependencies: - hasown "^2.0.0" - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -6188,191 +5969,17 @@ escodegen@^2.1.0: optionalDependencies: source-map "~0.6.1" -eslint-import-resolver-node@^0.3.9: - version "0.3.9" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" - integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== - dependencies: - debug "^3.2.7" - is-core-module "^2.13.0" - resolve "^1.22.4" - -eslint-import-resolver-typescript@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz#7b983680edd3f1c5bce1a5829ae0bc2d57fe9efa" - integrity sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg== - dependencies: - debug "^4.3.4" - enhanced-resolve "^5.12.0" - eslint-module-utils "^2.7.4" - fast-glob "^3.3.1" - get-tsconfig "^4.5.0" - is-core-module "^2.11.0" - is-glob "^4.0.3" - -eslint-module-utils@^2.7.4: - version "2.7.4" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" - integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== - dependencies: - debug "^3.2.7" - -eslint-module-utils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" - integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== - dependencies: - debug "^3.2.7" - -eslint-plugin-es@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz" - integrity sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ== - dependencies: - eslint-utils "^2.0.0" - regexpp "^3.0.0" - -eslint-plugin-import@^2.29.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" - integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== - dependencies: - array-includes "^3.1.7" - array.prototype.findlastindex "^1.2.3" - array.prototype.flat "^1.3.2" - array.prototype.flatmap "^1.3.2" - debug "^3.2.7" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.9" - eslint-module-utils "^2.8.0" - hasown "^2.0.0" - is-core-module "^2.13.1" - is-glob "^4.0.3" - minimatch "^3.1.2" - object.fromentries "^2.0.7" - object.groupby "^1.0.1" - object.values "^1.1.7" - semver "^6.3.1" - tsconfig-paths "^3.15.0" - -eslint-plugin-prettier@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz#17cfade9e732cef32b5f5be53bd4e07afd8e67e1" - integrity sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw== - dependencies: - prettier-linter-helpers "^1.0.0" - synckit "^0.8.6" - -eslint-plugin-vitest@^0.3.26: - version "0.3.26" - resolved "https://registry.yarnpkg.com/eslint-plugin-vitest/-/eslint-plugin-vitest-0.3.26.tgz#0906893c1f8f7094614fc6ff255c0a369cfbf427" - integrity sha512-oxe5JSPgRjco8caVLTh7Ti8PxpwJdhSV0hTQAmkFcNcmy/9DnqLB/oNVRA11RmVRP//2+jIIT6JuBEcpW3obYg== - dependencies: - "@typescript-eslint/utils" "^7.1.1" - -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-utils@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0: - version "1.3.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" - integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== - -eslint@^8.57.0: - version "8.57.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" - integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.0" - "@humanwhocodes/config-array" "^0.11.14" - "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" - ignore "^5.2.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" - esm@^3.2.25: version "3.2.25" resolved "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== - dependencies: - acorn "^8.9.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" - esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^5.1.0, estraverse@^5.2.0: +estraverse@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz" integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== @@ -6561,11 +6168,6 @@ extract-zip@^2.0.1: optionalDependencies: "@types/yauzl" "^2.9.1" -fast-content-type-parse@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz#4087162bf5af3294d4726ff29b334f72e3a1092c" - integrity sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ== - fast-decode-uri-component@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz" @@ -6581,17 +6183,12 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== - fast-fifo@^1.1.0, fast-fifo@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== -fast-glob@^3.2.9, fast-glob@^3.3.1: +fast-glob@^3.2.9: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -6607,36 +6204,19 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-json-stringify@^5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-5.7.0.tgz#b0a04c848fdeb6ecd83440c71a4db35067023bed" - integrity sha512-sBVPTgnAZseLu1Qgj6lUbQ0HfjFhZWXAmpZ5AaSGkyLh5gAXBga/uPJjQPHpDFjC9adWIpdOcCLSDTgrZ7snoQ== - dependencies: - "@fastify/deepmerge" "^1.0.0" - ajv "^8.10.0" - ajv-formats "^2.1.1" - fast-deep-equal "^3.1.3" - fast-uri "^2.1.0" - rfdc "^1.2.0" - -fast-json-stringify@^5.8.0: - version "5.13.0" - resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-5.13.0.tgz#3eafc02168713ef934d75000a8cf749492729fe8" - integrity sha512-XjTDWKHP3GoMQUOfnjYUbqeHeEt+PvYgvBdG2fRSmYaORILbSr8xTJvZX+w1YSAP5pw2NwKrGRmQleYueZEoxw== +fast-json-stringify@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-6.0.0.tgz#15c5e85b567ead695773bf55938b56aaaa57d805" + integrity sha512-FGMKZwniMTgZh7zQp9b6XnBVxUmKVahQLQeRQHqwYmPDqDhcEKZ3BaQsxelFFI5PY7nN71OEeiL47/zUWcYe1A== dependencies: - "@fastify/merge-json-schemas" "^0.1.0" - ajv "^8.10.0" - ajv-formats "^2.1.1" + "@fastify/merge-json-schemas" "^0.1.1" + ajv "^8.12.0" + ajv-formats "^3.0.1" fast-deep-equal "^3.1.3" - fast-uri "^2.1.0" + fast-uri "^2.3.0" json-schema-ref-resolver "^1.0.1" rfdc "^1.2.0" -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - fast-querystring@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/fast-querystring/-/fast-querystring-1.1.1.tgz#f4c56ef56b1a954880cfd8c01b83f9e1a3d3fda2" @@ -6654,37 +6234,41 @@ fast-safe-stringify@^2.1.1: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== -fast-uri@^2.0.0, fast-uri@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-2.2.0.tgz#519a0f849bef714aad10e9753d69d8f758f7445a" - integrity sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg== +fast-uri@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-2.4.0.tgz#67eae6fbbe9f25339d5d3f4c4234787b65d7d55e" + integrity sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA== -fastify-plugin@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-4.5.0.tgz#8b853923a0bba6ab6921bb8f35b81224e6988d91" - integrity sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg== +fast-uri@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.1.tgz#cddd2eecfc83a71c1be2cc2ef2061331be8a7134" + integrity sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw== -fastify@^4.27.0: - version "4.27.0" - resolved "https://registry.yarnpkg.com/fastify/-/fastify-4.27.0.tgz#e4a9b2a0a7b9efaeaf1140d47fdd4f91b5fcacb1" - integrity sha512-ci9IXzbigB8dyi0mSy3faa3Bsj0xWAPb9JeT4KRzubdSb6pNhcADRUaXCBml6V1Ss/a05kbtQls5LBmhHydoTA== +fastify-plugin@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/fastify-plugin/-/fastify-plugin-5.0.1.tgz#82d44e6fe34d1420bb5a4f7bee434d501e41939f" + integrity sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ== + +fastify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/fastify/-/fastify-5.0.0.tgz#f8f80bd741bde2de1997c25dbe31e61c91978111" + integrity sha512-Qe4dU+zGOzg7vXjw4EvcuyIbNnMwTmcuOhlOrOJsgwzvjEZmsM/IeHulgJk+r46STjdJS/ZJbxO8N70ODXDMEQ== dependencies: - "@fastify/ajv-compiler" "^3.5.0" - "@fastify/error" "^3.4.0" - "@fastify/fast-json-stringify-compiler" "^4.3.0" + "@fastify/ajv-compiler" "^4.0.0" + "@fastify/error" "^4.0.0" + "@fastify/fast-json-stringify-compiler" "^5.0.0" abstract-logging "^2.0.1" - avvio "^8.3.0" - fast-content-type-parse "^1.1.0" - fast-json-stringify "^5.8.0" - find-my-way "^8.0.0" - light-my-request "^5.11.0" + avvio "^9.0.0" + fast-json-stringify "^6.0.0" + find-my-way "^9.0.0" + light-my-request "^6.0.0" pino "^9.0.0" - process-warning "^3.0.0" + process-warning "^4.0.0" proxy-addr "^2.0.7" - rfdc "^1.3.0" + rfdc "^1.3.1" secure-json-parse "^2.7.0" - semver "^7.5.4" - toad-cache "^3.3.0" + semver "^7.6.0" + toad-cache "^3.7.0" fastq@^1.17.1: version "1.17.1" @@ -6735,13 +6319,6 @@ figures@^5.0.0: escape-string-regexp "^5.0.0" is-unicode-supported "^1.2.0" -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - file-stream-rotator@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz#007019e735b262bb6c6f0197e58e5c87cb96cec3" @@ -6768,14 +6345,21 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-my-way@^8.0.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-8.1.0.tgz#cc05e8e4b145322299d0de0a839b5be528c2083e" - integrity sha512-41QwjCGcVTODUmLLqTMeoHeiozbMXYMAE1CKFiDyi9zVZ2Vjh0yz3MF0WQZoIb+cmzP/XlbFjlF2NtJmvZHznA== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-my-way@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/find-my-way/-/find-my-way-9.0.1.tgz#991c3a7af36734480d48cd4ad0889ed168ed6c40" + integrity sha512-/5NN/R0pFWuff16TMajeKt2JyiW+/OE8nOO8vo1DwZTxLaIURb7lcBYPIgRPh61yCNh9l8voeKwcrkUzmB00vw== dependencies: fast-deep-equal "^3.1.3" fast-querystring "^1.0.0" - safe-regex2 "^2.0.0" + safe-regex2 "^4.0.0" find-up@5.0.0, find-up@^5.0.0: version "5.0.0" @@ -6808,24 +6392,11 @@ find-up@^6.3.0: locate-path "^7.1.0" path-exists "^5.0.0" -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - flat@^5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^3.1.0: - version "3.2.5" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" - integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== - fn.name@1.x.x: version "1.1.0" resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz" @@ -7127,13 +6698,6 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-tsconfig@^4.5.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.6.2.tgz#831879a5e6c2aa24fe79b60340e2233a1e0f472e" - integrity sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg== - dependencies: - resolve-pkg-maps "^1.0.0" - get-uri@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.2.tgz#e019521646f4a8ff6d291fbaea2c46da204bb75b" @@ -7198,13 +6762,6 @@ glob-parent@5.1.2, glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - glob@7.1.4: version "7.1.4" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" @@ -7263,6 +6820,18 @@ glob@^10.4.1: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +glob@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e" + integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g== + dependencies: + foreground-child "^3.1.0" + jackspeak "^4.0.1" + minimatch "^10.0.0" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^2.0.0" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -7308,13 +6877,6 @@ global-agent@^3.0.0: semver "^7.3.2" serialize-error "^7.0.1" -globals@^13.19.0: - version "13.20.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" - integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== - dependencies: - type-fest "^0.20.2" - globalthis@^1.0.1, globalthis@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" @@ -7322,7 +6884,7 @@ globalthis@^1.0.1, globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -globby@11.1.0, globby@^11.1.0: +globby@11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -7385,11 +6947,6 @@ grapheme-splitter@^1.0.2: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - graphql@^16.8.1: version "16.9.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.9.0.tgz#1c310e63f16a49ce1fbb230bd0a000e99f6f115f" @@ -7559,7 +7116,7 @@ http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0, http-cache-semantics@^ resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== -http-errors@2.0.0: +http-errors@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== @@ -7705,7 +7262,7 @@ ignore-walk@^6.0.0: dependencies: minimatch "^7.4.2" -ignore@^5.0.4, ignore@^5.1.1, ignore@^5.2.0, ignore@^5.2.4: +ignore@^5.0.4, ignore@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== @@ -7715,7 +7272,7 @@ immutable@^4.3.2: resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0" integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== -import-fresh@^3.2.1, import-fresh@^3.3.0: +import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -7941,7 +7498,7 @@ is-ci@3.0.1: dependencies: ci-info "^3.2.0" -is-core-module@^2.1.0, is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1, is-core-module@^2.3.0, is-core-module@^2.5.0, is-core-module@^2.8.1: +is-core-module@^2.1.0, is-core-module@^2.13.0, is-core-module@^2.5.0, is-core-module@^2.8.1: version "2.13.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== @@ -7980,7 +7537,7 @@ is-generator-function@^1.0.7: resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz" integrity sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: +is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -8063,11 +7620,6 @@ is-observable@^2.1.0: resolved "https://registry.npmjs.org/is-observable/-/is-observable-2.1.0.tgz" integrity sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw== -is-path-inside@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -8426,6 +7978,15 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jackspeak@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.1.tgz#9fca4ce961af6083e259c376e9e3541431f5287b" + integrity sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jake@^10.8.5: version "10.8.5" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" @@ -8564,23 +8125,11 @@ json-schema-traverse@^1.0.0: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" - integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== - dependencies: - minimist "^1.2.0" - json5@^2.2.2: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -8758,14 +8307,6 @@ level@^8.0.0: browser-level "^1.0.1" classic-level "^1.2.0" -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - libnpmaccess@7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-7.0.2.tgz#7f056c8c933dd9c8ba771fa6493556b53c5aac52" @@ -8815,14 +8356,14 @@ libp2p@1.4.3: multiformats "^13.1.0" uint8arrays "^5.0.3" -light-my-request@^5.11.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-5.12.0.tgz#e42ed02ddbfa587f82031b21459c6841a6948dfa" - integrity sha512-P526OX6E7aeCIfw/9UyJNsAISfcFETghysaWHQAlQYayynShT08MOj4c6fBCvTWBrHXSvqBAKDp3amUPSCQI4w== +light-my-request@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/light-my-request/-/light-my-request-6.0.0.tgz#97c6d0d5448ea2fc37836f0aefe94298f5a87dde" + integrity sha512-kFkFXrmKCL0EEeOmJybMH5amWFd+AFvlvMlvFTRxCUwbhfapZqDmeLMPoWihntnYY6JpoQDE9k+vOzObF1fDqg== dependencies: cookie "^0.6.0" - process-warning "^3.0.0" - set-cookie-parser "^2.4.1" + process-warning "^4.0.0" + set-cookie-parser "^2.6.0" lines-and-columns@^1.1.6: version "1.2.4" @@ -8947,11 +8488,6 @@ lodash.isplainobject@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - lodash.some@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz" @@ -9052,6 +8588,11 @@ lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +lru-cache@^11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.1.tgz#3a732fbfedb82c5ba7bca6564ad3f42afcb6e147" + integrity sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -9274,11 +8815,11 @@ micro-ftch@^0.3.1: integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg== micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" miller-rabin@^4.0.0: @@ -9306,7 +8847,7 @@ mime@2.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== -mime@^3.0.0: +mime@^3: version "3.0.0" resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== @@ -9365,14 +8906,14 @@ minimatch@5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@9.0.3, minimatch@^9.0.0, minimatch@^9.0.1: - version "9.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== +minimatch@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" + integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ== dependencies: brace-expansion "^2.0.1" -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -9400,13 +8941,27 @@ minimatch@^8.0.2: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.3, minimatch@^9.0.4: +minimatch@^9.0.0, minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.3: version "9.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimatch@~3.0.3: version "3.0.8" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" @@ -9554,10 +9109,10 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mnemonist@0.39.5: - version "0.39.5" - resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.39.5.tgz#5850d9b30d1b2bc57cc8787e5caa40f6c3420477" - integrity sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ== +mnemonist@0.39.8: + version "0.39.8" + resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.39.8.tgz#9078cd8386081afd986cca34b52b5d84ea7a4d38" + integrity sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ== dependencies: obliterator "^2.0.1" @@ -9724,11 +9279,6 @@ native-fetch@^4.0.2: resolved "https://registry.yarnpkg.com/native-fetch/-/native-fetch-4.0.2.tgz#75c8a44c5f3bb021713e5e24f2846750883e49af" integrity sha512-4QcVlKFtv2EYVS5MBgsGX5+NWKtbDbIECdUXDBGDMAZXq3Jkv9zf+y8iS7Ub8fEdga3GpYeazp9gauNqXHJOCg== -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - negotiator@^0.6.2, negotiator@^0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" @@ -10176,35 +9726,6 @@ object.assign@^4.1.4: has-symbols "^1.0.3" object-keys "^1.1.1" -object.fromentries@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" - integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -object.groupby@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.2.tgz#494800ff5bab78fd0eff2835ec859066e00192ec" - integrity sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw== - dependencies: - array.prototype.filter "^1.0.3" - call-bind "^1.0.5" - define-properties "^1.2.1" - es-abstract "^1.22.3" - es-errors "^1.0.0" - -object.values@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" - integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - obliterator@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.4.tgz#fa650e019b2d075d745e44f1effeb13a2adbe816" @@ -10262,23 +9783,11 @@ open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" -openapi-types@^12.0.0, openapi-types@^12.0.2: +openapi-types@^12.1.3: version "12.1.3" resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.3.tgz#471995eb26c4b97b7bd356aacf7b91b73e777dd3" integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== -optionator@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" - integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== - dependencies: - "@aashutoshrathi/word-wrap" "^1.2.3" - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - ora@^5.4.1: version "5.4.1" resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz" @@ -10358,7 +9867,7 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2, p-limit@^3.1.0: +p-limit@^3.0.2: version "3.1.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -10646,10 +10155,18 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-scurry@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" + integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + path-to-regexp@^6.2.0: - version "6.2.2" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz#324377a83e5049cbecadc5554d6a63a9a4866b36" - integrity sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw== + version "6.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" + integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== path-type@^3.0.0: version "3.0.0" @@ -10782,18 +10299,6 @@ postcss@^8.4.39: picocolors "^1.0.1" source-map-js "^1.2.0" -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - prettier@^3.2.5: version "3.2.5" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" @@ -10832,6 +10337,11 @@ process-warning@^3.0.0: resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== +process-warning@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-4.0.0.tgz#581e3a7a1fb456c5f4fd239f76bce75897682d5a" + integrity sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw== + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -11243,11 +10753,6 @@ regexp.prototype.flags@^1.5.1: define-properties "^1.2.0" set-function-name "^2.0.0" -regexpp@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -11285,12 +10790,7 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-pkg-maps@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" - integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== - -resolve@^1.10.0, resolve@^1.10.1, resolve@^1.17.0, resolve@^1.22.4, resolve@~1.22.1: +resolve@^1.10.0, resolve@^1.17.0, resolve@~1.22.1: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -11344,10 +10844,10 @@ restore-cursor@^4.0.0: onetime "^5.1.0" signal-exit "^3.0.2" -ret@~0.2.0: - version "0.2.2" - resolved "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz" - integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== +ret@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.5.0.tgz#30a4d38a7e704bd96dc5ffcbe7ce2a9274c41c95" + integrity sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw== retry@^0.12.0: version "0.12.0" @@ -11373,10 +10873,10 @@ rewiremock@^3.14.5: wipe-node-cache "^2.1.2" wipe-webpack-cache "^2.1.0" -rfdc@^1.1.4, rfdc@^1.2.0, rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== +rfdc@^1.1.4, rfdc@^1.2.0, rfdc@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== rgb2hex@0.2.5: version "0.2.5" @@ -11442,28 +10942,28 @@ rollup-plugin-visualizer@^5.12.0: yargs "^17.5.1" rollup@^4.13.0: - version "4.16.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.16.1.tgz#5a60230987fe95ebe68bab517297c116dbb1a88d" - integrity sha512-5CaD3MPDlPKfhqzRvWXK96G6ELJfPZNb3LHiZxTHgDdC6jvwfGz2E8nY+9g1ONk4ttHsK1WaFP19Js4PSr1E3g== + version "4.22.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.22.4.tgz#4135a6446671cd2a2453e1ad42a45d5973ec3a0f" + integrity sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A== dependencies: "@types/estree" "1.0.5" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.16.1" - "@rollup/rollup-android-arm64" "4.16.1" - "@rollup/rollup-darwin-arm64" "4.16.1" - "@rollup/rollup-darwin-x64" "4.16.1" - "@rollup/rollup-linux-arm-gnueabihf" "4.16.1" - "@rollup/rollup-linux-arm-musleabihf" "4.16.1" - "@rollup/rollup-linux-arm64-gnu" "4.16.1" - "@rollup/rollup-linux-arm64-musl" "4.16.1" - "@rollup/rollup-linux-powerpc64le-gnu" "4.16.1" - "@rollup/rollup-linux-riscv64-gnu" "4.16.1" - "@rollup/rollup-linux-s390x-gnu" "4.16.1" - "@rollup/rollup-linux-x64-gnu" "4.16.1" - "@rollup/rollup-linux-x64-musl" "4.16.1" - "@rollup/rollup-win32-arm64-msvc" "4.16.1" - "@rollup/rollup-win32-ia32-msvc" "4.16.1" - "@rollup/rollup-win32-x64-msvc" "4.16.1" + "@rollup/rollup-android-arm-eabi" "4.22.4" + "@rollup/rollup-android-arm64" "4.22.4" + "@rollup/rollup-darwin-arm64" "4.22.4" + "@rollup/rollup-darwin-x64" "4.22.4" + "@rollup/rollup-linux-arm-gnueabihf" "4.22.4" + "@rollup/rollup-linux-arm-musleabihf" "4.22.4" + "@rollup/rollup-linux-arm64-gnu" "4.22.4" + "@rollup/rollup-linux-arm64-musl" "4.22.4" + "@rollup/rollup-linux-powerpc64le-gnu" "4.22.4" + "@rollup/rollup-linux-riscv64-gnu" "4.22.4" + "@rollup/rollup-linux-s390x-gnu" "4.22.4" + "@rollup/rollup-linux-x64-gnu" "4.22.4" + "@rollup/rollup-linux-x64-musl" "4.22.4" + "@rollup/rollup-win32-arm64-msvc" "4.22.4" + "@rollup/rollup-win32-ia32-msvc" "4.22.4" + "@rollup/rollup-win32-x64-msvc" "4.22.4" fsevents "~2.3.2" rrweb-cssom@^0.6.0: @@ -11536,12 +11036,12 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -safe-regex2@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz" - integrity sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ== +safe-regex2@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/safe-regex2/-/safe-regex2-4.0.0.tgz#5e04d8362cd4884753c8bce9715d4759a5239c0a" + integrity sha512-Hvjfv25jPDVr3U+4LDzBuZPPOymELG3PYcSk5hcevooo1yxxamQL/bHs/GrEPGmMoMEwRrHVGiCA1pXi97B8Ew== dependencies: - ret "~0.2.0" + ret "~0.5.0" safe-stable-stringify@^2.3.1: version "2.4.2" @@ -11597,7 +11097,7 @@ semver@7.5.3: dependencies: lru-cache "^6.0.0" -semver@^6.1.0, semver@^6.2.0, semver@^6.3.1: +semver@^6.1.0, semver@^6.2.0: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -11640,10 +11140,10 @@ set-blocking@^2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-cookie-parser@^2.4.1: - version "2.4.8" - resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz" - integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== +set-cookie-parser@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz#ef5552b56dc01baae102acb5fc9fb8cd060c30f9" + integrity sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ== set-function-length@^1.1.1: version "1.2.0" @@ -12205,7 +11705,7 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@3.1.1, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: +strip-json-comments@3.1.1, strip-json-comments@~3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -12286,25 +11786,12 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -synckit@^0.8.6: - version "0.8.8" - resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.8.tgz#fe7fe446518e3d3d49f5e429f443cf08b6edfcd7" - integrity sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ== - dependencies: - "@pkgr/core" "^0.1.0" - tslib "^2.6.2" - systeminformation@^5.22.9: version "5.22.9" resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.22.9.tgz#68700a895a48cbf96e2cd6a34c5027d1fe58f053" integrity sha512-qUWJhQ9JSBhdjzNUQywpvc0icxUAjMY3sZqUoS0GOtaJV9Ijq8s9zEP8Gaqmymn1dOefcICyPXK1L3kgKxlUpg== -tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -tar-fs@^3.0.4, tar-fs@^3.0.5, tar-fs@^3.0.6: +tar-fs@^3.0.4, tar-fs@^3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.6.tgz#eaccd3a67d5672f09ca8e8f9c3d2b89fa173f217" integrity sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w== @@ -12315,6 +11802,17 @@ tar-fs@^3.0.4, tar-fs@^3.0.5, tar-fs@^3.0.6: bare-fs "^2.1.1" bare-path "^2.1.0" +tar-fs@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.5.tgz#f954d77767e4e6edf973384e1eb95f8f81d64ed9" + integrity sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg== + dependencies: + pump "^3.0.0" + tar-stream "^3.1.5" + optionalDependencies: + bare-fs "^2.1.1" + bare-path "^2.1.0" + tar-fs@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" @@ -12420,11 +11918,6 @@ text-hex@1.0.x: resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" - integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= - thread-stream@^2.6.0: version "2.7.0" resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11" @@ -12515,7 +12008,7 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toad-cache@^3.3.0: +toad-cache@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/toad-cache/-/toad-cache-3.7.0.tgz#b9b63304ea7c45ec34d91f1d2fa513517025c441" integrity sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw== @@ -12576,16 +12069,6 @@ triple-beam@^1.3.0: resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== -ts-api-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d" - integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A== - -ts-api-utils@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" - integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== - ts-node@^10.8.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -12624,16 +12107,6 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tsconfig-paths@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" - integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - tsconfig-paths@^4.1.2: version "4.2.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz#ef78e19039133446d244beac0fd6a1632e2d107c" @@ -12663,7 +12136,7 @@ tslib@^1.10.0: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.6.1, tslib@^2.6.2: +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== @@ -12697,13 +12170,6 @@ tweetnacl@^0.14.3: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - type-fest@2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.13.0.tgz#d1ecee38af29eb2e863b22299a3d68ef30d2abfb" @@ -12719,11 +12185,6 @@ type-fest@^0.18.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -12876,6 +12337,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + undici@^5.12.0: version "5.28.4" resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" @@ -13122,9 +12588,9 @@ vite-plugin-top-level-await@^1.4.2: uuid "^10.0.0" vite@^5.0.0, vite@^5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.4.tgz#b36ebd47c8a5e3a8727046375d5f10bf9fdf8715" - integrity sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA== + version "5.3.6" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.6.tgz#e097c0a7b79adb2e60bec9ef7907354f09d027bd" + integrity sha512-es78AlrylO8mTVBygC0gTC0FENv0C6T496vvd33ydbjF/mIi9q3XQ9A3NWo5qLGFKywvz10J26813OkLvcQleA== dependencies: esbuild "^0.21.3" postcss "^8.4.39" @@ -13829,10 +13295,10 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^2.2.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" - integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== +yaml@^2.2.2, yaml@^2.4.1, yaml@^2.4.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" + integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q== yargs-parser@20.2.4: version "20.2.4"