mirror of
https://github.com/ChainSafe/lodestar.git
synced 2026-01-09 15:48:08 -05:00
Merge branch 'unstable' into te/peerDAS_merge_unstable_may_14
This commit is contained in:
13
.github/workflows/assets/kurtosis_sim_test_config.yaml
vendored
Normal file
13
.github/workflows/assets/kurtosis_sim_test_config.yaml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
participants:
|
||||
- el_type: geth
|
||||
cl_type: lodestar
|
||||
cl_image: chainsafe/lodestar:kurtosis-ci
|
||||
- el_type: geth
|
||||
cl_type: lighthouse
|
||||
additional_services:
|
||||
- assertoor
|
||||
assertoor_params:
|
||||
run_block_proposal_check: true
|
||||
run_transaction_test: true
|
||||
run_blob_transaction_test: true
|
||||
run_opcodes_transaction_test: true
|
||||
27
.github/workflows/kurtosis.yml
vendored
Normal file
27
.github/workflows/kurtosis.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Kurtosis sim tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *" # Runs at midnight UTC everyday
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
name: Build and run Kurtosis
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build Docker image - local only
|
||||
run: >
|
||||
docker buildx build . --load \
|
||||
--tag chainsafe/lodestar:kurtosis-ci \
|
||||
--build-arg COMMIT=$(git rev-parse HEAD)
|
||||
- name: Run test
|
||||
uses: ethpandaops/kurtosis-assertoor-github-action@v1
|
||||
with:
|
||||
ethereum_package_args: '.github/workflows/assets/kurtosis_sim_test_config.yaml'
|
||||
@@ -4838,7 +4838,7 @@
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": false
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
@@ -4856,7 +4856,8 @@
|
||||
"expr": "rate(lodestar_gossip_block_elapsed_time_till_received_sum[$rate_interval]) / rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval])",
|
||||
"format": "heatmap",
|
||||
"instant": false,
|
||||
"legendFormat": "time",
|
||||
"interval": "",
|
||||
"legendFormat": "{{source}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -218,10 +219,12 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "auto"
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.1.1",
|
||||
"pluginVersion": "10.4.1",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -287,10 +290,12 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "value"
|
||||
"textMode": "value",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.1.1",
|
||||
"pluginVersion": "10.4.1",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -339,10 +344,12 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "name"
|
||||
"textMode": "name",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.1.1",
|
||||
"pluginVersion": "10.4.1",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -394,10 +401,12 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "name"
|
||||
"textMode": "name",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.1.1",
|
||||
"pluginVersion": "10.4.1",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -413,7 +422,6 @@
|
||||
}
|
||||
],
|
||||
"title": "Lodestar version",
|
||||
"transformations": [],
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
@@ -449,10 +457,12 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "name"
|
||||
"textMode": "name",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.1.1",
|
||||
"pluginVersion": "10.4.1",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -480,6 +490,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -692,6 +703,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -776,6 +788,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -948,6 +961,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -1029,6 +1043,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -1137,6 +1152,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -1220,6 +1236,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -1304,6 +1321,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -1365,10 +1383,12 @@
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "rate(lodestar_gossip_block_elapsed_time_till_received_sum[$rate_interval])\n/\nrate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval])",
|
||||
"expr": "rate(lodestar_gossip_block_elapsed_time_till_received_sum{source=\"gossip\"}[$rate_interval])\n/\nrate(lodestar_gossip_block_elapsed_time_till_received_count{source=\"gossip\"}[$rate_interval])",
|
||||
"interval": "",
|
||||
"legendFormat": "till received",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
@@ -1398,6 +1418,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -1508,6 +1529,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -1588,6 +1610,7 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
@@ -8764,8 +8787,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "10s",
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"schemaVersion": 39,
|
||||
"tags": [
|
||||
"lodestar",
|
||||
"debug"
|
||||
|
||||
@@ -3327,7 +3327,7 @@
|
||||
"exemplar": false,
|
||||
"expr": "(\n rate(lodestar_gossip_block_elapsed_time_till_processed_sum[$rate_interval])\n -\n rate(lodestar_gossip_block_elapsed_time_till_received_sum[$rate_interval])\n)\n/\nrate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval])",
|
||||
"interval": "",
|
||||
"legendFormat": "processed minus received",
|
||||
"legendFormat": "processed minus received {{source}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
@@ -3421,10 +3421,12 @@
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "rate(lodestar_gossip_block_elapsed_time_till_received_sum[$rate_interval])\n/\nrate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval])",
|
||||
"interval": "",
|
||||
"legendFormat": "till received",
|
||||
"legendFormat": "till received {{source}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
@@ -5370,8 +5372,8 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"0.5\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"legendFormat": "<=0.5s",
|
||||
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"0.5\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"legendFormat": "<=0.5s {{source}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
@@ -5381,9 +5383,9 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"1\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"1\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"hide": false,
|
||||
"legendFormat": "<=1s",
|
||||
"legendFormat": "<=1s {{source}}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
},
|
||||
@@ -5393,9 +5395,9 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"2\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"2\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"hide": false,
|
||||
"legendFormat": "<=2s",
|
||||
"legendFormat": "<=2s {{source}}",
|
||||
"range": true,
|
||||
"refId": "C"
|
||||
},
|
||||
@@ -5405,9 +5407,10 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"4\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"4\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"hide": false,
|
||||
"legendFormat": "<=4s",
|
||||
"interval": "",
|
||||
"legendFormat": "<=4s {{source}}",
|
||||
"range": true,
|
||||
"refId": "D"
|
||||
},
|
||||
@@ -5417,9 +5420,10 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"6\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"6\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
|
||||
"hide": false,
|
||||
"legendFormat": "<=6s",
|
||||
"interval": "",
|
||||
"legendFormat": "<=6s {{source}}",
|
||||
"range": true,
|
||||
"refId": "E"
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "(sum(nodejs_heap_size_total_bytes) + sum(discv5_worker_nodejs_heap_size_total_bytes) + sum(network_worker_nodejs_heap_size_total_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_total_bytes))\nor\n(sum(nodejs_heap_size_total_bytes) + sum(discv5_worker_nodejs_heap_size_total_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_total_bytes))",
|
||||
"expr": "(sum(nodejs_heap_size_total_bytes) + sum(discv5_worker_nodejs_heap_size_total_bytes) + sum(network_worker_nodejs_heap_size_total_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_total_bytes or vector(0)))\nor\n(sum(nodejs_heap_size_total_bytes) + sum(discv5_worker_nodejs_heap_size_total_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_total_bytes or vector(0)))",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "node allocated heap",
|
||||
@@ -192,7 +192,7 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "(sum(nodejs_heap_size_used_bytes) + sum(discv5_worker_nodejs_heap_size_used_bytes) + sum(network_worker_nodejs_heap_size_used_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_used_bytes)) \nor\n(sum(nodejs_heap_size_used_bytes) + sum(discv5_worker_nodejs_heap_size_used_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_used_bytes)) ",
|
||||
"expr": "(sum(nodejs_heap_size_used_bytes) + sum(discv5_worker_nodejs_heap_size_used_bytes) + sum(network_worker_nodejs_heap_size_used_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_used_bytes or vector(0))) \nor\n(sum(nodejs_heap_size_used_bytes) + sum(discv5_worker_nodejs_heap_size_used_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_used_bytes or vector(0))) ",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "node used heap",
|
||||
@@ -206,7 +206,7 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "(sum(nodejs_external_memory_bytes) + sum(discv5_worker_nodejs_external_memory_bytes) + sum(network_worker_nodejs_external_memory_bytes) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes))\nor\n(sum(nodejs_external_memory_bytes) + sum(discv5_worker_nodejs_external_memory_bytes) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes))",
|
||||
"expr": "(sum(nodejs_external_memory_bytes) + sum(discv5_worker_nodejs_external_memory_bytes) + sum(network_worker_nodejs_external_memory_bytes) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes or vector(0)))\nor\n(sum(nodejs_external_memory_bytes) + sum(discv5_worker_nodejs_external_memory_bytes) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes or vector(0)))",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "node external memory",
|
||||
@@ -307,7 +307,7 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "(nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + discv5_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + network_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + lodestar_historical_state_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"})\nor\n(nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + discv5_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + lodestar_historical_state_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"})",
|
||||
"expr": "(sum(nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"}) + sum(discv5_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"}) + sum(network_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"}) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} or vector(0)))\nor\nsum(nodejs_external_memory_bytes{job=~\"$validator_job|validator\"})",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "external_memory",
|
||||
@@ -1196,7 +1196,7 @@
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"title": "HIstorical State Worker Thread - GC pause time rate + reclaimed bytes",
|
||||
"title": "Historical State Worker Thread - GC pause time rate + reclaimed bytes",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,18 @@
|
||||
|
||||
This section of the documentation will cover common questions and common encounters by users and developers.
|
||||
|
||||
## Tooling
|
||||
## Developer Tooling
|
||||
|
||||
### Python Distutils
|
||||
|
||||
:::note "MacOS: ModuleNotFoundError: No module named 'distutils'"
|
||||
When using the `yarn` command, some MacOS users may experience this error if they are running Python 3.12+. The `distutils` module has been removed from the standard Python library via PEP 632 which deprecates and eliminates `distutils` in favor of other tools like `setuptools`.
|
||||
|
||||
For MacOS, there is no direct equivalent package to Linux's `python3-distutils`. Therefore, the solution is to install the `python-setuptools` package via Homebrew: `brew install python-setuptools`
|
||||
|
||||
:::
|
||||
|
||||
### Yarn Package Manager
|
||||
|
||||
:::note "Package manager issues"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useNx": true,
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"stream": true,
|
||||
"command": {
|
||||
"version": {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -72,10 +72,10 @@
|
||||
"dependencies": {
|
||||
"@chainsafe/persistent-merkle-tree": "^1.1.0",
|
||||
"@chainsafe/ssz": "^1.2.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"eventsource": "^2.0.2",
|
||||
"qs": "^6.11.1"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ContainerType, ValueOf} from "@chainsafe/ssz";
|
||||
import {ChainForkConfig} from "@lodestar/config";
|
||||
import {ChainForkConfig, SpecJson} from "@lodestar/config";
|
||||
import {ssz} from "@lodestar/types";
|
||||
import {
|
||||
ArrayOf,
|
||||
@@ -24,7 +24,7 @@ export const ForkListType = ArrayOf(ssz.phase0.Fork);
|
||||
|
||||
export type DepositContract = ValueOf<typeof DepositContractType>;
|
||||
export type ForkList = ValueOf<typeof ForkListType>;
|
||||
export type Spec = Record<string, string>;
|
||||
export type Spec = SpecJson;
|
||||
|
||||
export type Endpoints = {
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -117,18 +117,18 @@
|
||||
"@libp2p/peer-id": "^5.1.0",
|
||||
"@libp2p/prometheus-metrics": "^4.3.15",
|
||||
"@libp2p/tcp": "^10.1.8",
|
||||
"@lodestar/api": "^1.29.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/db": "^1.29.0",
|
||||
"@lodestar/fork-choice": "^1.29.0",
|
||||
"@lodestar/light-client": "^1.29.0",
|
||||
"@lodestar/logger": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/reqresp": "^1.29.0",
|
||||
"@lodestar/state-transition": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/validator": "^1.29.0",
|
||||
"@lodestar/api": "^1.30.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/db": "^1.30.0",
|
||||
"@lodestar/fork-choice": "^1.30.0",
|
||||
"@lodestar/light-client": "^1.30.0",
|
||||
"@lodestar/logger": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/reqresp": "^1.30.0",
|
||||
"@lodestar/state-transition": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"@lodestar/validator": "^1.30.0",
|
||||
"@multiformats/multiaddr": "^12.1.3",
|
||||
"c-kzg": "^4.1.0",
|
||||
"datastore-core": "^10.0.2",
|
||||
|
||||
@@ -15,7 +15,7 @@ import {specConstants} from "./constants.js";
|
||||
* [altair](https://github.com/ethereum/consensus.0-specs/blob/v1.1.10/presets/mainnet/altair.yaml) values
|
||||
* - Configuration for the beacon node, for example the [mainnet](https://github.com/ethereum/consensus-specs/blob/v1.1.10/configs/mainnet.yaml) values
|
||||
*/
|
||||
export function renderJsonSpec(config: ChainConfig): Record<string, string> {
|
||||
export function renderJsonSpec(config: ChainConfig): routes.config.Spec {
|
||||
const configJson = chainConfigToJson(config);
|
||||
const presetJson = presetToJson(activePreset);
|
||||
const constantsJson = specValuesToJson(specConstants);
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
BeaconBlock,
|
||||
BlindedBeaconBlock,
|
||||
BlockContents,
|
||||
Bytes32,
|
||||
CommitteeIndex,
|
||||
Epoch,
|
||||
ProducedBlockSource,
|
||||
@@ -75,7 +76,7 @@ import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossi
|
||||
import {CommitteeSubscription} from "../../../network/subnets/index.js";
|
||||
import {SyncState} from "../../../sync/index.js";
|
||||
import {isOptimisticBlock} from "../../../util/forkChoice.js";
|
||||
import {getDefaultGraffiti, toGraffitiBuffer} from "../../../util/graffiti.js";
|
||||
import {getDefaultGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
|
||||
import {getLodestarClientVersion} from "../../../util/metadata.js";
|
||||
import {ApiOptions} from "../../options.js";
|
||||
import {getStateResponseWithRegen} from "../beacon/state/utils.js";
|
||||
@@ -399,25 +400,15 @@ export function getValidatorApi(
|
||||
async function produceBuilderBlindedBlock(
|
||||
slot: Slot,
|
||||
randaoReveal: BLSSignature,
|
||||
graffiti?: string,
|
||||
graffiti: Bytes32,
|
||||
// as of now fee recipient checks can not be performed because builder does not return bid recipient
|
||||
{
|
||||
skipHeadChecksAndUpdate,
|
||||
commonBlockBody,
|
||||
parentBlockRoot: inParentBlockRoot,
|
||||
}: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> &
|
||||
(
|
||||
| {
|
||||
skipHeadChecksAndUpdate: true;
|
||||
commonBlockBody: CommonBlockBody;
|
||||
parentBlockRoot: Root;
|
||||
}
|
||||
| {
|
||||
skipHeadChecksAndUpdate?: false | undefined;
|
||||
commonBlockBody?: undefined;
|
||||
parentBlockRoot?: undefined;
|
||||
}
|
||||
) = {}
|
||||
parentBlockRoot,
|
||||
}: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> & {
|
||||
commonBlockBody: CommonBlockBody;
|
||||
parentBlockRoot: Root;
|
||||
}
|
||||
): Promise<ProduceBlindedBlockRes> {
|
||||
const version = config.getForkName(slot);
|
||||
if (!isForkPostBellatrix(version)) {
|
||||
@@ -435,17 +426,6 @@ export function getValidatorApi(
|
||||
throw Error("Execution builder disabled");
|
||||
}
|
||||
|
||||
let parentBlockRoot: Root;
|
||||
if (skipHeadChecksAndUpdate !== true) {
|
||||
notWhileSyncing();
|
||||
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
||||
|
||||
parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot);
|
||||
} else {
|
||||
parentBlockRoot = inParentBlockRoot;
|
||||
}
|
||||
notOnOutOfRangeData(parentBlockRoot);
|
||||
|
||||
let timer: undefined | ((opts: {source: ProducedBlockSource}) => number);
|
||||
try {
|
||||
timer = metrics?.blockProductionTime.startTimer();
|
||||
@@ -453,9 +433,7 @@ export function getValidatorApi(
|
||||
slot,
|
||||
parentBlockRoot,
|
||||
randaoReveal,
|
||||
graffiti: toGraffitiBuffer(
|
||||
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
|
||||
),
|
||||
graffiti,
|
||||
commonBlockBody,
|
||||
});
|
||||
|
||||
@@ -482,37 +460,20 @@ export function getValidatorApi(
|
||||
async function produceEngineFullBlockOrContents(
|
||||
slot: Slot,
|
||||
randaoReveal: BLSSignature,
|
||||
graffiti?: string,
|
||||
graffiti: Bytes32,
|
||||
{
|
||||
feeRecipient,
|
||||
strictFeeRecipientCheck,
|
||||
skipHeadChecksAndUpdate,
|
||||
commonBlockBody,
|
||||
parentBlockRoot: inParentBlockRoot,
|
||||
}: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> &
|
||||
(
|
||||
| {
|
||||
skipHeadChecksAndUpdate: true;
|
||||
commonBlockBody: CommonBlockBody;
|
||||
parentBlockRoot: Root;
|
||||
}
|
||||
| {skipHeadChecksAndUpdate?: false | undefined; commonBlockBody?: undefined; parentBlockRoot?: undefined}
|
||||
) = {}
|
||||
parentBlockRoot,
|
||||
}: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> & {
|
||||
commonBlockBody: CommonBlockBody;
|
||||
parentBlockRoot: Root;
|
||||
}
|
||||
): Promise<ProduceBlockOrContentsRes & {shouldOverrideBuilder?: boolean}> {
|
||||
const source = ProducedBlockSource.engine;
|
||||
metrics?.blockProductionRequests.inc({source});
|
||||
|
||||
let parentBlockRoot: Root;
|
||||
if (skipHeadChecksAndUpdate !== true) {
|
||||
notWhileSyncing();
|
||||
await waitForSlot(slot); // Must never request for a future slot > currentSlot
|
||||
|
||||
parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot);
|
||||
} else {
|
||||
parentBlockRoot = inParentBlockRoot;
|
||||
}
|
||||
notOnOutOfRangeData(parentBlockRoot);
|
||||
|
||||
let timer: undefined | ((opts: {source: ProducedBlockSource}) => number);
|
||||
try {
|
||||
timer = metrics?.blockProductionTime.startTimer();
|
||||
@@ -520,9 +481,7 @@ export function getValidatorApi(
|
||||
slot,
|
||||
parentBlockRoot,
|
||||
randaoReveal,
|
||||
graffiti: toGraffitiBuffer(
|
||||
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
|
||||
),
|
||||
graffiti,
|
||||
feeRecipient,
|
||||
commonBlockBody,
|
||||
});
|
||||
@@ -614,6 +573,10 @@ export function getValidatorApi(
|
||||
);
|
||||
}
|
||||
|
||||
const graffitiBytes = toGraffitiBytes(
|
||||
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
|
||||
);
|
||||
|
||||
const loggerContext = {
|
||||
slot,
|
||||
fork,
|
||||
@@ -630,9 +593,7 @@ export function getValidatorApi(
|
||||
slot,
|
||||
parentBlockRoot,
|
||||
randaoReveal,
|
||||
graffiti: toGraffitiBuffer(
|
||||
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
|
||||
),
|
||||
graffiti: graffitiBytes,
|
||||
});
|
||||
logger.debug("Produced common block body", loggerContext);
|
||||
|
||||
@@ -651,23 +612,19 @@ export function getValidatorApi(
|
||||
// Start calls for building execution and builder blocks
|
||||
|
||||
const builderPromise = isBuilderEnabled
|
||||
? produceBuilderBlindedBlock(slot, randaoReveal, graffiti, {
|
||||
? produceBuilderBlindedBlock(slot, randaoReveal, graffitiBytes, {
|
||||
feeRecipient,
|
||||
// can't do fee recipient checks as builder bid doesn't return feeRecipient as of now
|
||||
strictFeeRecipientCheck: false,
|
||||
// skip checking and recomputing head in these individual produce calls
|
||||
skipHeadChecksAndUpdate: true,
|
||||
commonBlockBody,
|
||||
parentBlockRoot,
|
||||
})
|
||||
: Promise.reject(new Error("Builder disabled"));
|
||||
|
||||
const enginePromise = isEngineEnabled
|
||||
? produceEngineFullBlockOrContents(slot, randaoReveal, graffiti, {
|
||||
? produceEngineFullBlockOrContents(slot, randaoReveal, graffitiBytes, {
|
||||
feeRecipient,
|
||||
strictFeeRecipientCheck,
|
||||
// skip checking and recomputing head in these individual produce calls
|
||||
skipHeadChecksAndUpdate: true,
|
||||
commonBlockBody,
|
||||
parentBlockRoot,
|
||||
}).then((engineBlock) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {ModuleThread} from "@chainsafe/threads";
|
||||
import {BeaconConfig} from "@lodestar/config";
|
||||
import {BeaconConfig, SpecJson} from "@lodestar/config";
|
||||
import {LoggerNode, LoggerNodeOpts} from "@lodestar/logger/node";
|
||||
import {BeaconStateTransitionMetrics} from "@lodestar/state-transition";
|
||||
import {Gauge, Histogram} from "@lodestar/utils";
|
||||
@@ -20,7 +20,7 @@ export type HistoricalStateRegenModules = HistoricalStateRegenInitModules & {
|
||||
};
|
||||
|
||||
export type HistoricalStateWorkerData = {
|
||||
chainConfigJson: Record<string, string>;
|
||||
chainConfigJson: SpecJson;
|
||||
genesisValidatorsRoot: Uint8Array;
|
||||
genesisTime: number;
|
||||
maxConcurrency: number;
|
||||
|
||||
@@ -39,8 +39,8 @@ export async function archiveBlocks(
|
||||
): Promise<void> {
|
||||
// Use fork choice to determine the blocks to archive and delete
|
||||
// getAllAncestorBlocks response includes the finalized block, so it's also moved to the cold db
|
||||
const finalizedCanonicalBlocks = forkChoice.getAllAncestorBlocks(finalizedCheckpoint.rootHex);
|
||||
const finalizedNonCanonicalBlocks = forkChoice.getAllNonAncestorBlocks(finalizedCheckpoint.rootHex);
|
||||
const {ancestors: finalizedCanonicalBlocks, nonAncestors: finalizedNonCanonicalBlocks} =
|
||||
forkChoice.getAllAncestorAndNonAncestorBlocks(finalizedCheckpoint.rootHex);
|
||||
|
||||
// NOTE: The finalized block will be exactly the first block of `epoch` or previous
|
||||
const finalizedPostDeneb = finalizedCheckpoint.epoch >= config.DENEB_FORK_EPOCH;
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
createCachedBeaconState,
|
||||
getEffectiveBalanceIncrementsZeroInactive,
|
||||
isCachedBeaconState,
|
||||
processSlots,
|
||||
} from "@lodestar/state-transition";
|
||||
import {
|
||||
BeaconBlock,
|
||||
@@ -1256,12 +1257,14 @@ export class BeaconChain implements IBeaconChain {
|
||||
}
|
||||
|
||||
async getBlockRewards(block: BeaconBlock | BlindedBeaconBlock): Promise<BlockRewards> {
|
||||
const preState = this.regen.getPreStateSync(block);
|
||||
let preState = this.regen.getPreStateSync(block);
|
||||
|
||||
if (preState === null) {
|
||||
throw Error(`Pre-state is unavailable given block's parent root ${toRootHex(block.parentRoot)}`);
|
||||
}
|
||||
|
||||
preState = processSlots(preState, block.slot); // Dial preState's slot to block.slot
|
||||
|
||||
const postState = this.regen.getStateSync(toRootHex(block.stateRoot)) ?? undefined;
|
||||
|
||||
return computeBlockRewards(block, preState.clone(), postState?.clone());
|
||||
@@ -1297,12 +1300,14 @@ export class BeaconChain implements IBeaconChain {
|
||||
block: BeaconBlock | BlindedBeaconBlock,
|
||||
validatorIds?: (ValidatorIndex | string)[]
|
||||
): Promise<SyncCommitteeRewards> {
|
||||
const preState = this.regen.getPreStateSync(block);
|
||||
let preState = this.regen.getPreStateSync(block);
|
||||
|
||||
if (preState === null) {
|
||||
throw Error(`Pre-state is unavailable given block's parent root ${toRootHex(block.parentRoot)}`);
|
||||
}
|
||||
|
||||
preState = processSlots(preState, block.slot); // Dial preState's slot to block.slot
|
||||
|
||||
return computeSyncCommitteeRewards(block, preState.clone(), validatorIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
PayloadId,
|
||||
getExpectedGasLimit,
|
||||
} from "../../execution/index.js";
|
||||
import {fromGraffitiBuffer} from "../../util/graffiti.js";
|
||||
import {fromGraffitiBytes} from "../../util/graffiti.js";
|
||||
import {ckzg} from "../../util/kzg.js";
|
||||
import type {BeaconChain} from "../chain.js";
|
||||
import {CommonBlockBody} from "../interface.js";
|
||||
@@ -163,7 +163,7 @@ export async function produceBlockBody<T extends BlockType>(
|
||||
} = blockBody;
|
||||
|
||||
Object.assign(logMeta, {
|
||||
graffiti: fromGraffitiBuffer(graffiti),
|
||||
graffiti: fromGraffitiBytes(graffiti),
|
||||
attestations: attestations.length,
|
||||
deposits: deposits.length,
|
||||
voluntaryExits: voluntaryExits.length,
|
||||
|
||||
@@ -5,10 +5,16 @@ import {Metrics} from "../../metrics/index.js";
|
||||
import {isSuperSetOrEqual} from "../../util/bitArray.js";
|
||||
|
||||
/**
|
||||
* With this gossip validation condition: [IGNORE] aggregate.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
|
||||
* Since ATTESTATION_PROPAGATION_SLOT_RANGE is 32, we keep seen AggregateAndProof in the last 2 epochs.
|
||||
* With this gossip validation condition:
|
||||
* pre-deneb:
|
||||
* - [IGNORE] aggregate.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
|
||||
* post-deneb:
|
||||
* - [IGNORE] the epoch of `aggregate.data.slot` is either the current or previous epoch
|
||||
* - [IGNORE] `aggregate.data.slot` is equal to or earlier than the `current_slot` (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
|
||||
*
|
||||
* We keep seen AggregateAndProof in the last 2 epochs pre and post deneb.
|
||||
*/
|
||||
const MAX_EPOCHS_IN_CACHE = 2;
|
||||
const EPOCH_LOOKBACK_LIMIT = 2;
|
||||
|
||||
export type AggregationInfo = {
|
||||
aggregationBits: BitArray;
|
||||
@@ -64,7 +70,7 @@ export class SeenAggregatedAttestations {
|
||||
}
|
||||
|
||||
prune(currentEpoch: Epoch): void {
|
||||
this.lowestPermissibleEpoch = Math.max(currentEpoch - MAX_EPOCHS_IN_CACHE, 0);
|
||||
this.lowestPermissibleEpoch = Math.max(currentEpoch - EPOCH_LOOKBACK_LIMIT, 0);
|
||||
for (const epoch of this.aggregateRootsByEpoch.keys()) {
|
||||
if (epoch < this.lowestPermissibleEpoch) {
|
||||
this.aggregateRootsByEpoch.delete(epoch);
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import {Epoch, ValidatorIndex} from "@lodestar/types";
|
||||
import {MapDef} from "@lodestar/utils";
|
||||
|
||||
// The next, current and previous epochs. We require the next epoch due to the
|
||||
// `MAXIMUM_GOSSIP_CLOCK_DISPARITY`. We require the previous epoch since the
|
||||
// specification delcares:
|
||||
// How many *non future* epochs we intend to keep for SeenAttesters.
|
||||
// Pre and post deneb specs require us to accept attestations from current and
|
||||
// previous epoch.
|
||||
//
|
||||
// ```
|
||||
// aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE
|
||||
// >= current_slot >= aggregate.data.slot
|
||||
// ```
|
||||
// Pre-deneb:
|
||||
// - `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot`
|
||||
//
|
||||
// This means that during the current epoch we will always accept an attestation
|
||||
// from at least one slot in the previous epoch.
|
||||
const MAX_EPOCHS = 3;
|
||||
// Post-deneb:
|
||||
// - `attestation.data.slot <= current_slot`
|
||||
// - `compute_epoch_at_slot(attestation.data.slot) in (get_previous_epoch(state), get_current_epoch(state))`
|
||||
//
|
||||
// When factored in MAXIMUM_GOSSIP_CLOCK_DISPARITY, it is possible we keep 3 epochs of SeenAttesters:
|
||||
// previous, current and future epoch. This constant is solely used to calculate `lowestPermissibleEpoch`
|
||||
// which prunes anything older than it.
|
||||
//
|
||||
// Assuming we're at epoch 100 while all other nodes at epoch 99, they all accept attestations at epoch 98, 99.
|
||||
// If MAX_RETAINED_EPOCH = 2 then our lowestPermissibleEpoch is 98 which is fine
|
||||
//
|
||||
// Assuming we're at epoch 99 while all other nodes at epoch 100, they all accept attestations at epoch 99, 100.
|
||||
// If MAX_RETAINED_EPOCH = 2 then lowestPermissibleEpoch is 97 which is more than enough
|
||||
const EPOCH_LOOKBACK_LIMIT = 2;
|
||||
|
||||
/**
|
||||
* Keeps a cache to filter unaggregated attestations from the same validator in the same epoch.
|
||||
@@ -34,7 +43,7 @@ export class SeenAttesters {
|
||||
}
|
||||
|
||||
prune(currentEpoch: Epoch): void {
|
||||
this.lowestPermissibleEpoch = Math.max(currentEpoch - MAX_EPOCHS, 0);
|
||||
this.lowestPermissibleEpoch = Math.max(currentEpoch - EPOCH_LOOKBACK_LIMIT, 0);
|
||||
for (const epoch of this.validatorIndexesByEpoch.keys()) {
|
||||
if (epoch < this.lowestPermissibleEpoch) {
|
||||
this.validatorIndexesByEpoch.delete(epoch);
|
||||
|
||||
@@ -104,9 +104,17 @@ async function validateAggregateAndProof(
|
||||
throw new AttestationError(GossipAction.REJECT, {code: AttestationErrorCode.BAD_TARGET_EPOCH});
|
||||
}
|
||||
|
||||
// Pre-deneb:
|
||||
// [IGNORE] aggregate.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
|
||||
// -- i.e. aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot
|
||||
// (a client MAY queue future aggregates for processing at the appropriate slot).
|
||||
// Post-deneb:
|
||||
// [IGNORE] `aggregate.data.slot` is equal to or earlier than the `current_slot` (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
|
||||
// -- i.e. `aggregate.data.slot <= current_slot`
|
||||
// (a client MAY queue future aggregates for processing at the appropriate slot).
|
||||
// [IGNORE] the epoch of `aggregate.data.slot` is either the current or previous epoch
|
||||
// (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
|
||||
// -- i.e. `compute_epoch_at_slot(aggregate.data.slot) in (get_previous_epoch(state), get_current_epoch(state))`
|
||||
verifyPropagationSlotRange(fork, chain, attSlot);
|
||||
}
|
||||
|
||||
|
||||
@@ -320,9 +320,17 @@ async function validateAttestationNoSignatureCheck(
|
||||
});
|
||||
}
|
||||
|
||||
// Pre-deneb:
|
||||
// [IGNORE] attestation.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (within a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
|
||||
// -- i.e. attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot
|
||||
// (a client MAY queue future attestations for processing at the appropriate slot).
|
||||
// Post-deneb:
|
||||
// [IGNORE] `attestation.data.slot` is equal to or earlier than the `current_slot` (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
|
||||
// -- i.e. `attestation.data.slot <= current_slot`
|
||||
// (a client MAY queue future attestation for processing at the appropriate slot).
|
||||
// [IGNORE] the epoch of `attestation.data.slot` is either the current or previous epoch
|
||||
// (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
|
||||
// -- i.e. `compute_epoch_at_slot(attestation.data.slot) in (get_previous_epoch(state), get_current_epoch(state))`
|
||||
verifyPropagationSlotRange(fork, chain, attestationOrCache.attestation.data.slot);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,11 @@ import {
|
||||
KZG_COMMITMENT_SUBTREE_INDEX0,
|
||||
isForkPostElectra,
|
||||
} from "@lodestar/params";
|
||||
import {computeStartSlotAtEpoch, getBlockHeaderProposerSignatureSet} from "@lodestar/state-transition";
|
||||
import {
|
||||
computeEpochAtSlot,
|
||||
computeStartSlotAtEpoch,
|
||||
getBlockHeaderProposerSignatureSet,
|
||||
} from "@lodestar/state-transition";
|
||||
import {BlobIndex, Root, Slot, SubnetID, deneb, ssz} from "@lodestar/types";
|
||||
import {toRootHex, verifyMerkleBranch} from "@lodestar/utils";
|
||||
|
||||
@@ -25,7 +29,7 @@ export async function validateGossipBlobSidecar(
|
||||
const blobSlot = blobSidecar.signedBlockHeader.message.slot;
|
||||
|
||||
// [REJECT] The sidecar's index is consistent with `MAX_BLOBS_PER_BLOCK` -- i.e. `blob_sidecar.index < MAX_BLOBS_PER_BLOCK`.
|
||||
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(fork);
|
||||
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(blobSlot));
|
||||
if (blobSidecar.index >= maxBlobsPerBlock) {
|
||||
throw new BlobSidecarGossipError(GossipAction.REJECT, {
|
||||
code: BlobSidecarErrorCode.INDEX_TOO_LARGE,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {ChainForkConfig} from "@lodestar/config";
|
||||
import {ForkName, isForkPostDeneb} from "@lodestar/params";
|
||||
import {
|
||||
computeEpochAtSlot,
|
||||
computeStartSlotAtEpoch,
|
||||
computeTimeAtSlot,
|
||||
getBlockProposerSignatureSet,
|
||||
@@ -113,7 +114,7 @@ export async function validateGossipBlock(
|
||||
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
|
||||
if (isForkPostDeneb(fork)) {
|
||||
const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
|
||||
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(fork);
|
||||
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(blockSlot));
|
||||
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
||||
throw new BlockGossipError(GossipAction.REJECT, {
|
||||
code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {PeerScoreStatsDump} from "@chainsafe/libp2p-gossipsub/score";
|
||||
import {PublishOpts} from "@chainsafe/libp2p-gossipsub/types";
|
||||
import {routes} from "@lodestar/api";
|
||||
import {SpecJson} from "@lodestar/config";
|
||||
import {LoggerNodeOpts} from "@lodestar/logger/node";
|
||||
import {ResponseIncoming} from "@lodestar/reqresp";
|
||||
import {phase0} from "@lodestar/types";
|
||||
@@ -75,7 +76,7 @@ export interface INetworkCore extends INetworkCorePublic {
|
||||
export type NetworkWorkerData = {
|
||||
// TODO: Review if NetworkOptions is safe for passing
|
||||
opts: NetworkOptions;
|
||||
chainConfigJson: Record<string, string>;
|
||||
chainConfigJson: SpecJson;
|
||||
genesisValidatorsRoot: Uint8Array;
|
||||
genesisTime: number;
|
||||
activeValidatorCount: number;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {BeaconConfig} from "@lodestar/config";
|
||||
import {LoggerNode} from "@lodestar/logger/node";
|
||||
import {ForkSeq, NUMBER_OF_COLUMNS} from "@lodestar/params";
|
||||
import {ResponseIncoming} from "@lodestar/reqresp";
|
||||
import {computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition";
|
||||
import {computeEpochAtSlot, computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition";
|
||||
import {
|
||||
AttesterSlashing,
|
||||
ColumnIndex,
|
||||
@@ -550,11 +550,11 @@ export class Network implements INetwork {
|
||||
peerId: PeerIdStr,
|
||||
request: deneb.BlobSidecarsByRangeRequest
|
||||
): Promise<deneb.BlobSidecar[]> {
|
||||
const fork = this.config.getForkName(request.startSlot);
|
||||
const epoch = computeEpochAtSlot(request.startSlot);
|
||||
return collectMaxResponseTyped(
|
||||
this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRange, [Version.V1], request),
|
||||
// request's count represent the slots, so the actual max count received could be slots * blobs per slot
|
||||
request.count * this.config.getMaxBlobsPerBlock(fork),
|
||||
request.count * this.config.getMaxBlobsPerBlock(epoch),
|
||||
responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRange]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,10 +112,6 @@ export enum ReprocessRejectReason {
|
||||
* Cannot accept work reason for metrics
|
||||
*/
|
||||
export enum CannotAcceptWorkReason {
|
||||
/**
|
||||
* Validating or processing gossip block at current slot.
|
||||
*/
|
||||
processingCurrentSlotBlock = "processing_current_slot_block",
|
||||
/**
|
||||
* bls is busy.
|
||||
*/
|
||||
@@ -160,7 +156,6 @@ export class NetworkProcessor {
|
||||
// to be stored in this Map and reprocessed once the block comes
|
||||
private readonly awaitingGossipsubMessagesByRootBySlot: MapDef<Slot, MapDef<RootHex, Set<PendingGossipsubMessage>>>;
|
||||
private unknownBlockGossipsubMessagesCount = 0;
|
||||
private isProcessingCurrentSlotBlock = false;
|
||||
private unknownRootsBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
|
||||
|
||||
constructor(
|
||||
@@ -269,15 +264,6 @@ export class NetworkProcessor {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
slot === clockSlot &&
|
||||
(topicType === GossipType.beacon_block ||
|
||||
topicType === GossipType.blob_sidecar ||
|
||||
topicType === GossipType.data_column_sidecar)
|
||||
) {
|
||||
// in the worse case if the current slot block is not valid, this will be reset in the next slot
|
||||
this.isProcessingCurrentSlotBlock = true;
|
||||
}
|
||||
message.msgSlot = slot;
|
||||
// check if we processed a block with this root
|
||||
// no need to check if root is a descendant of the current finalized block, it will be checked once we validate the message if needed
|
||||
@@ -324,7 +310,6 @@ export class NetworkProcessor {
|
||||
block: string;
|
||||
executionOptimistic: boolean;
|
||||
}): Promise<void> {
|
||||
this.isProcessingCurrentSlotBlock = false;
|
||||
const byRootGossipsubMessages = this.awaitingGossipsubMessagesByRootBySlot.getOrDefault(slot);
|
||||
const waitingGossipsubMessages = byRootGossipsubMessages.getOrDefault(rootHex);
|
||||
if (waitingGossipsubMessages.size === 0) {
|
||||
@@ -351,7 +336,6 @@ export class NetworkProcessor {
|
||||
}
|
||||
|
||||
private onClockSlot(clockSlot: Slot): void {
|
||||
this.isProcessingCurrentSlotBlock = false;
|
||||
const nowSec = Date.now() / 1000;
|
||||
for (const [slot, gossipMessagesByRoot] of this.awaitingGossipsubMessagesByRootBySlot.entries()) {
|
||||
if (slot < clockSlot) {
|
||||
@@ -498,10 +482,6 @@ export class NetworkProcessor {
|
||||
* Return null if chain can accept work, otherwise return the reason why it cannot accept work
|
||||
*/
|
||||
private checkAcceptWork(): null | CannotAcceptWorkReason {
|
||||
if (this.isProcessingCurrentSlotBlock) {
|
||||
return CannotAcceptWorkReason.processingCurrentSlotBlock;
|
||||
}
|
||||
|
||||
if (!this.chain.blsThreadPoolCanAcceptWork()) {
|
||||
return CannotAcceptWorkReason.bls;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import {Bytes32} from "@lodestar/types";
|
||||
import {GRAFFITI_SIZE} from "../constants/index.js";
|
||||
import {ClientVersion} from "../execution/index.js";
|
||||
|
||||
/**
|
||||
* Parses a graffiti UTF8 string and returns a 32 bytes buffer right padded with zeros
|
||||
*/
|
||||
export function toGraffitiBuffer(graffiti: string): Buffer {
|
||||
export function toGraffitiBytes(graffiti: string): Bytes32 {
|
||||
return Buffer.concat([Buffer.from(graffiti, "utf8"), Buffer.alloc(GRAFFITI_SIZE, 0)], GRAFFITI_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a graffiti from 32 bytes buffer back to a UTF-8 string
|
||||
*/
|
||||
export function fromGraffitiBuffer(graffiti: Uint8Array): string {
|
||||
export function fromGraffitiBytes(graffiti: Bytes32): string {
|
||||
return Buffer.from(graffiti.buffer, graffiti.byteOffset, graffiti.byteLength)
|
||||
.toString("utf8")
|
||||
.replaceAll("\u0000", "");
|
||||
|
||||
@@ -55,6 +55,7 @@ vi.mock("@lodestar/fork-choice", async (importActual) => {
|
||||
getBlock: vi.fn(),
|
||||
getAllAncestorBlocks: vi.fn(),
|
||||
getAllNonAncestorBlocks: vi.fn(),
|
||||
getAllAncestorAndNonAncestorBlocks: vi.fn(),
|
||||
iterateAncestorBlocks: vi.fn(),
|
||||
getBlockSummariesByParentRoot: vi.fn(),
|
||||
getCanonicalBlockAtSlot: vi.fn(),
|
||||
|
||||
@@ -13,7 +13,7 @@ import {CommonBlockBody} from "../../../../../src/chain/interface.js";
|
||||
import {BlockType, produceBlockBody} from "../../../../../src/chain/produceBlock/index.js";
|
||||
import {PayloadIdCache} from "../../../../../src/execution/index.js";
|
||||
import {SyncState} from "../../../../../src/sync/interface.js";
|
||||
import {toGraffitiBuffer} from "../../../../../src/util/graffiti.js";
|
||||
import {toGraffitiBytes} from "../../../../../src/util/graffiti.js";
|
||||
import {ApiTestModules, getApiTestModules} from "../../../../utils/api.js";
|
||||
import {generateCachedBellatrixState, zeroProtoBlock} from "../../../../utils/state.js";
|
||||
import {generateProtoBlock} from "../../../../utils/typeGenerator.js";
|
||||
@@ -200,7 +200,7 @@ describe("api/validator - produceBlockV3", () => {
|
||||
await api.produceBlockV3({slot, randaoReveal, graffiti, feeRecipient});
|
||||
expect(modules.chain.produceBlock).toBeCalledWith({
|
||||
randaoReveal,
|
||||
graffiti: toGraffitiBuffer(graffiti),
|
||||
graffiti: toGraffitiBytes(graffiti),
|
||||
slot,
|
||||
parentBlockRoot,
|
||||
feeRecipient,
|
||||
@@ -211,7 +211,7 @@ describe("api/validator - produceBlockV3", () => {
|
||||
await api.produceBlockV3({slot, randaoReveal, graffiti});
|
||||
expect(modules.chain.produceBlock).toBeCalledWith({
|
||||
randaoReveal,
|
||||
graffiti: toGraffitiBuffer(graffiti),
|
||||
graffiti: toGraffitiBytes(graffiti),
|
||||
slot,
|
||||
parentBlockRoot,
|
||||
feeRecipient: undefined,
|
||||
@@ -253,7 +253,7 @@ describe("api/validator - produceBlockV3", () => {
|
||||
// use fee recipient passed in produceBlockBody call for payload gen in engine notifyForkchoiceUpdate
|
||||
await produceBlockBody.call(modules.chain as unknown as BeaconChain, BlockType.Full, state, {
|
||||
randaoReveal,
|
||||
graffiti: toGraffitiBuffer(graffiti),
|
||||
graffiti: toGraffitiBytes(graffiti),
|
||||
slot,
|
||||
feeRecipient,
|
||||
parentSlot: slot - 1,
|
||||
@@ -278,7 +278,7 @@ describe("api/validator - produceBlockV3", () => {
|
||||
modules.chain["beaconProposerCache"].getOrDefault.mockReturnValue("0x fee recipient address");
|
||||
await produceBlockBody.call(modules.chain as unknown as BeaconChain, BlockType.Full, state, {
|
||||
randaoReveal,
|
||||
graffiti: toGraffitiBuffer(graffiti),
|
||||
graffiti: toGraffitiBytes(graffiti),
|
||||
slot,
|
||||
parentSlot: slot - 1,
|
||||
parentBlockRoot: fromHexString(ZERO_HASH_HEX),
|
||||
|
||||
@@ -50,8 +50,10 @@ describe("block archiver task", () => {
|
||||
const canonicalBlocks = [blocks[4], blocks[3], blocks[1], blocks[0]];
|
||||
const nonCanonicalBlocks = [blocks[2]];
|
||||
const currentEpoch = 8;
|
||||
vi.spyOn(forkChoiceStub, "getAllAncestorBlocks").mockReturnValue(canonicalBlocks);
|
||||
vi.spyOn(forkChoiceStub, "getAllNonAncestorBlocks").mockReturnValue(nonCanonicalBlocks);
|
||||
vi.spyOn(forkChoiceStub, "getAllAncestorAndNonAncestorBlocks").mockReturnValue({
|
||||
ancestors: canonicalBlocks,
|
||||
nonAncestors: nonCanonicalBlocks,
|
||||
});
|
||||
await archiveBlocks(
|
||||
config,
|
||||
dbStub,
|
||||
|
||||
@@ -27,7 +27,10 @@ describe("gossip block validation", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// Fill up with kzg commitments
|
||||
block.body.blobKzgCommitments = Array.from({length: config.MAX_BLOBS_PER_BLOCK}, () => new Uint8Array([0]));
|
||||
block.body.blobKzgCommitments = Array.from(
|
||||
{length: config.BLOB_SCHEDULE[0].MAX_BLOBS_PER_BLOCK},
|
||||
() => new Uint8Array([0])
|
||||
);
|
||||
|
||||
chain = getMockedBeaconChain();
|
||||
vi.spyOn(chain.clock, "currentSlotWithGossipDisparity", "get").mockReturnValue(clockSlot);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {describe, expect, it} from "vitest";
|
||||
import {ClientCode} from "../../../src/execution/index.js";
|
||||
import {getDefaultGraffiti, toGraffitiBuffer} from "../../../src/util/graffiti.js";
|
||||
import {getDefaultGraffiti, toGraffitiBytes} from "../../../src/util/graffiti.js";
|
||||
|
||||
describe("Graffiti helper", () => {
|
||||
describe("toGraffitiBuffer", () => {
|
||||
@@ -23,7 +23,7 @@ describe("Graffiti helper", () => {
|
||||
];
|
||||
for (const {input, result} of cases) {
|
||||
it(`Convert graffiti UTF8 ${input} to Buffer`, () => {
|
||||
expect(toGraffitiBuffer(input).toString("hex")).toBe(result);
|
||||
expect(Buffer.from(toGraffitiBytes(input)).toString("hex")).toBe(result);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig {
|
||||
BELLATRIX_FORK_EPOCH: 0,
|
||||
CAPELLA_FORK_EPOCH: 0,
|
||||
DENEB_FORK_EPOCH: forkEpoch,
|
||||
BLOB_SCHEDULE: [{EPOCH: forkEpoch, MAX_BLOBS_PER_BLOCK: 6}],
|
||||
});
|
||||
case ForkName.electra:
|
||||
return createChainForkConfig({
|
||||
@@ -37,6 +38,10 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig {
|
||||
CAPELLA_FORK_EPOCH: 0,
|
||||
DENEB_FORK_EPOCH: 0,
|
||||
ELECTRA_FORK_EPOCH: forkEpoch,
|
||||
BLOB_SCHEDULE: [
|
||||
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 6},
|
||||
{EPOCH: forkEpoch, MAX_BLOBS_PER_BLOCK: 9},
|
||||
],
|
||||
});
|
||||
case ForkName.fulu:
|
||||
return createChainForkConfig({
|
||||
@@ -46,6 +51,10 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig {
|
||||
DENEB_FORK_EPOCH: 0,
|
||||
ELECTRA_FORK_EPOCH: 0,
|
||||
FULU_FORK_EPOCH: forkEpoch,
|
||||
BLOB_SCHEDULE: [
|
||||
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 6},
|
||||
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 9},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@chainsafe/lodestar",
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"description": "Command line interface for lodestar",
|
||||
"author": "ChainSafe Systems",
|
||||
"license": "LGPL-3.0",
|
||||
@@ -62,17 +62,17 @@
|
||||
"@libp2p/crypto": "^5.0.15",
|
||||
"@libp2p/interface": "^2.7.0",
|
||||
"@libp2p/peer-id": "^5.1.0",
|
||||
"@lodestar/api": "^1.29.0",
|
||||
"@lodestar/beacon-node": "^1.29.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/db": "^1.29.0",
|
||||
"@lodestar/light-client": "^1.29.0",
|
||||
"@lodestar/logger": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/state-transition": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/validator": "^1.29.0",
|
||||
"@lodestar/api": "^1.30.0",
|
||||
"@lodestar/beacon-node": "^1.30.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/db": "^1.30.0",
|
||||
"@lodestar/light-client": "^1.30.0",
|
||||
"@lodestar/logger": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/state-transition": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"@lodestar/validator": "^1.30.0",
|
||||
"@multiformats/multiaddr": "^12.1.3",
|
||||
"deepmerge": "^4.3.1",
|
||||
"ethers": "^6.7.0",
|
||||
@@ -87,7 +87,7 @@
|
||||
"yargs": "^17.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lodestar/test-utils": "^1.29.0",
|
||||
"@lodestar/test-utils": "^1.30.0",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/inquirer": "^9.0.3",
|
||||
"@types/proper-lockfile": "^4.1.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lodestar/config",
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"description": "Chain configuration required for lodestar",
|
||||
"author": "ChainSafe Systems",
|
||||
"license": "Apache-2.0",
|
||||
@@ -65,8 +65,8 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@chainsafe/ssz": "^1.2.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0"
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export const chainConfig: ChainConfig = {
|
||||
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,
|
||||
BLOB_SIDECAR_SUBNET_COUNT: 6,
|
||||
MAX_BLOBS_PER_BLOCK: 6,
|
||||
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
|
||||
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[0].MAX_BLOBS_PER_BLOCK
|
||||
MAX_REQUEST_BLOB_SIDECARS: 768,
|
||||
|
||||
// Electra
|
||||
@@ -119,7 +119,7 @@ export const chainConfig: ChainConfig = {
|
||||
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000,
|
||||
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9,
|
||||
MAX_BLOBS_PER_BLOCK_ELECTRA: 9,
|
||||
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA
|
||||
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[1].MAX_BLOBS_PER_BLOCK
|
||||
MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152,
|
||||
|
||||
// Fulu
|
||||
@@ -130,5 +130,13 @@ export const chainConfig: ChainConfig = {
|
||||
NODE_CUSTODY_REQUIREMENT: 1,
|
||||
VALIDATOR_CUSTODY_REQUIREMENT: 8,
|
||||
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000,
|
||||
MAX_BLOBS_PER_BLOCK_FULU: 12,
|
||||
|
||||
// Blob Scheduling
|
||||
// ---------------------------------------------------------------
|
||||
BLOB_SCHEDULE: [
|
||||
// Deneb
|
||||
{EPOCH: 269568, MAX_BLOBS_PER_BLOCK: 6},
|
||||
// Electra
|
||||
{EPOCH: 364032, MAX_BLOBS_PER_BLOCK: 9},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -105,7 +105,7 @@ export const chainConfig: ChainConfig = {
|
||||
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,
|
||||
BLOB_SIDECAR_SUBNET_COUNT: 6,
|
||||
MAX_BLOBS_PER_BLOCK: 6,
|
||||
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
|
||||
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[0].MAX_BLOBS_PER_BLOCK
|
||||
MAX_REQUEST_BLOB_SIDECARS: 768,
|
||||
|
||||
// Electra
|
||||
@@ -115,7 +115,7 @@ export const chainConfig: ChainConfig = {
|
||||
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000,
|
||||
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9,
|
||||
MAX_BLOBS_PER_BLOCK_ELECTRA: 9,
|
||||
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA
|
||||
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[1].MAX_BLOBS_PER_BLOCK
|
||||
MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152,
|
||||
|
||||
// Fulu
|
||||
@@ -126,5 +126,13 @@ export const chainConfig: ChainConfig = {
|
||||
NODE_CUSTODY_REQUIREMENT: 1,
|
||||
VALIDATOR_CUSTODY_REQUIREMENT: 8,
|
||||
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000,
|
||||
MAX_BLOBS_PER_BLOCK_FULU: 12,
|
||||
|
||||
// Blob Scheduling
|
||||
// ---------------------------------------------------------------
|
||||
BLOB_SCHEDULE: [
|
||||
// Deneb
|
||||
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: 6},
|
||||
// Electra
|
||||
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: 9},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import {fromHex, toHex} from "@lodestar/utils";
|
||||
import {ChainConfig, SpecValue, SpecValueTypeName, chainConfigTypes} from "./types.js";
|
||||
import {
|
||||
BlobScheduleEntry,
|
||||
ChainConfig,
|
||||
SpecJson,
|
||||
SpecValue,
|
||||
SpecValueTypeName,
|
||||
chainConfigTypes,
|
||||
isBlobSchedule,
|
||||
} from "./types.js";
|
||||
|
||||
const MAX_UINT64_JSON = "18446744073709551615";
|
||||
|
||||
export function chainConfigToJson(config: ChainConfig): Record<string, string> {
|
||||
const json: Record<string, string> = {};
|
||||
export function chainConfigToJson(config: ChainConfig): SpecJson {
|
||||
const json: SpecJson = {};
|
||||
|
||||
for (const key of Object.keys(chainConfigTypes) as (keyof ChainConfig)[]) {
|
||||
const value = config[key];
|
||||
@@ -29,8 +37,8 @@ export function chainConfigFromJson(json: Record<string, unknown>): ChainConfig
|
||||
return config;
|
||||
}
|
||||
|
||||
export function specValuesToJson(spec: Record<string, SpecValue>): Record<string, string> {
|
||||
const json: Record<string, string> = {};
|
||||
export function specValuesToJson(spec: Record<string, SpecValue>): SpecJson {
|
||||
const json: SpecJson = {};
|
||||
|
||||
for (const key of Object.keys(spec)) {
|
||||
json[key] = serializeSpecValue(spec[key], toSpecValueTypeName(spec[key]));
|
||||
@@ -45,10 +53,14 @@ export function toSpecValueTypeName(value: SpecValue): SpecValueTypeName {
|
||||
if (typeof value === "number") return "number";
|
||||
if (typeof value === "bigint") return "bigint";
|
||||
if (typeof value === "string") return "string";
|
||||
if (isBlobSchedule(value)) return "blob_schedule";
|
||||
throw Error(`Unknown value type ${value}`);
|
||||
}
|
||||
|
||||
export function serializeSpecValue(value: SpecValue, typeName: SpecValueTypeName): string {
|
||||
export function serializeSpecValue(
|
||||
value: SpecValue,
|
||||
typeName: SpecValueTypeName
|
||||
): string | Record<keyof BlobScheduleEntry, string>[] {
|
||||
switch (typeName) {
|
||||
case "number":
|
||||
if (typeof value !== "number") {
|
||||
@@ -76,12 +88,64 @@ export function serializeSpecValue(value: SpecValue, typeName: SpecValueTypeName
|
||||
throw Error(`Invalid value ${value.toString()} expected string`);
|
||||
}
|
||||
return value;
|
||||
|
||||
case "blob_schedule":
|
||||
if (!isBlobSchedule(value)) {
|
||||
throw Error(`Invalid value ${value.toString()} expected BlobSchedule`);
|
||||
}
|
||||
|
||||
return value.map(({EPOCH, MAX_BLOBS_PER_BLOCK}) => ({
|
||||
EPOCH: EPOCH === Infinity ? MAX_UINT64_JSON : EPOCH.toString(10),
|
||||
MAX_BLOBS_PER_BLOCK: MAX_BLOBS_PER_BLOCK === Infinity ? MAX_UINT64_JSON : MAX_BLOBS_PER_BLOCK.toString(10),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export function deserializeSpecValue(valueStr: unknown, typeName: SpecValueTypeName, keyName: string): SpecValue {
|
||||
if (typeName === "blob_schedule") {
|
||||
if (!Array.isArray(valueStr)) {
|
||||
throw Error(`Invalid BLOB_SCHEDULE value ${valueStr} expected array`);
|
||||
}
|
||||
|
||||
const blobSchedule = valueStr.map((entry, i) => {
|
||||
if (typeof entry !== "object" || entry === null) {
|
||||
throw Error(`Invalid BLOB_SCHEDULE[${i}] entry ${entry} expected object`);
|
||||
}
|
||||
|
||||
const out = {} as BlobScheduleEntry;
|
||||
|
||||
for (const key of ["EPOCH", "MAX_BLOBS_PER_BLOCK"] as Array<keyof BlobScheduleEntry>) {
|
||||
const value = entry[key];
|
||||
|
||||
if (value === undefined) {
|
||||
throw Error(`Invalid BLOB_SCHEDULE[${i}] entry ${JSON.stringify(entry)} missing ${key}`);
|
||||
}
|
||||
|
||||
if (typeof value !== "string") {
|
||||
throw Error(`Invalid BLOB_SCHEDULE[${i}].${key} value ${value} expected string`);
|
||||
}
|
||||
|
||||
if (value === MAX_UINT64_JSON) {
|
||||
out[key] = Infinity;
|
||||
} else {
|
||||
const parsed = parseInt(value, 10);
|
||||
|
||||
if (Number.isNaN(parsed)) {
|
||||
throw Error(`Invalid BLOB_SCHEDULE[${i}].${key} value ${value} expected number`);
|
||||
}
|
||||
|
||||
out[key] = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
});
|
||||
|
||||
return blobSchedule;
|
||||
}
|
||||
|
||||
if (typeof valueStr !== "string") {
|
||||
throw Error(`Invalid ${keyName} value ${valueStr as string} expected string`);
|
||||
throw Error(`Invalid ${keyName} value ${valueStr} expected string`);
|
||||
}
|
||||
|
||||
switch (typeName) {
|
||||
|
||||
@@ -66,6 +66,14 @@ export const gnosisChainConfig: ChainConfig = {
|
||||
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 64000000000,
|
||||
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 2,
|
||||
MAX_BLOBS_PER_BLOCK_ELECTRA: 2,
|
||||
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA
|
||||
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[1].MAX_BLOBS_PER_BLOCK
|
||||
MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 256,
|
||||
|
||||
// Blob Scheduling
|
||||
BLOB_SCHEDULE: [
|
||||
// Deneb
|
||||
{EPOCH: 889856, MAX_BLOBS_PER_BLOCK: 2},
|
||||
// Electra
|
||||
{EPOCH: 1337856, MAX_BLOBS_PER_BLOCK: 2},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -89,7 +89,9 @@ export type ChainConfig = {
|
||||
NODE_CUSTODY_REQUIREMENT: number;
|
||||
VALIDATOR_CUSTODY_REQUIREMENT: number;
|
||||
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: number;
|
||||
MAX_BLOBS_PER_BLOCK_FULU: number;
|
||||
|
||||
// Blob Scheduling
|
||||
BLOB_SCHEDULE: BlobSchedule;
|
||||
};
|
||||
|
||||
export const chainConfigTypes: SpecTypes<ChainConfig> = {
|
||||
@@ -171,11 +173,33 @@ export const chainConfigTypes: SpecTypes<ChainConfig> = {
|
||||
NODE_CUSTODY_REQUIREMENT: "number",
|
||||
VALIDATOR_CUSTODY_REQUIREMENT: "number",
|
||||
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: "number",
|
||||
MAX_BLOBS_PER_BLOCK_FULU: "number",
|
||||
|
||||
// Blob Scheduling
|
||||
BLOB_SCHEDULE: "blob_schedule",
|
||||
};
|
||||
|
||||
export type BlobScheduleEntry = {
|
||||
EPOCH: number;
|
||||
MAX_BLOBS_PER_BLOCK: number;
|
||||
};
|
||||
|
||||
export type BlobSchedule = BlobScheduleEntry[];
|
||||
|
||||
export function isBlobSchedule(value: unknown): value is BlobSchedule {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.every(
|
||||
(entry) =>
|
||||
typeof entry === "object" &&
|
||||
entry !== null &&
|
||||
typeof entry.EPOCH === "number" &&
|
||||
typeof entry.MAX_BLOBS_PER_BLOCK === "number"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** Allows values in a Spec file */
|
||||
export type SpecValue = number | bigint | Uint8Array | string;
|
||||
export type SpecValue = number | bigint | Uint8Array | string | BlobSchedule;
|
||||
|
||||
/** Type value name of each spec field. Numbers are ignored since they are the most common */
|
||||
export type SpecValueType<V extends SpecValue> = V extends number
|
||||
@@ -186,7 +210,9 @@ export type SpecValueType<V extends SpecValue> = V extends number
|
||||
? "bytes"
|
||||
: V extends string
|
||||
? "string"
|
||||
: never;
|
||||
: V extends BlobSchedule
|
||||
? "blob_schedule"
|
||||
: never;
|
||||
|
||||
/** All possible type names for a SpecValue */
|
||||
export type SpecValueTypeName = SpecValueType<SpecValue>;
|
||||
@@ -194,3 +220,5 @@ export type SpecValueTypeName = SpecValueType<SpecValue>;
|
||||
export type SpecTypes<Spec extends Record<string, SpecValue>> = {
|
||||
[K in keyof Spec]: SpecValueType<Spec[K]>;
|
||||
};
|
||||
|
||||
export type SpecJson = Record<string, string | Record<string, string>[]>;
|
||||
|
||||
@@ -138,15 +138,37 @@ export function createForkConfig(config: ChainConfig): ForkConfig {
|
||||
}
|
||||
return sszTypesFor(forkName);
|
||||
},
|
||||
getMaxBlobsPerBlock(fork: ForkName): number {
|
||||
getMaxBlobsPerBlock(epoch: Epoch): number {
|
||||
// TODO Fulu: Max blobs of Deneb and Electra are hardcoded for fusaka devnet-0. Remove this for devnet-1
|
||||
const fork = this.getForkInfoAtEpoch(epoch).name;
|
||||
|
||||
switch (fork) {
|
||||
case ForkName.fulu:
|
||||
return config.MAX_BLOBS_PER_BLOCK_FULU;
|
||||
case ForkName.electra:
|
||||
return config.MAX_BLOBS_PER_BLOCK_ELECTRA;
|
||||
default:
|
||||
case ForkName.deneb:
|
||||
return config.MAX_BLOBS_PER_BLOCK;
|
||||
}
|
||||
|
||||
if (config.BLOB_SCHEDULE.length === 0) {
|
||||
throw Error("Attempt to get MAX_BLOBS_PER_BLOCK from empty BLOB_SCHEDULE");
|
||||
}
|
||||
|
||||
// Sort by epoch in descending order to find the latest applicable value
|
||||
const blobSchedule = [...config.BLOB_SCHEDULE].sort((a, b) => {
|
||||
if (a.EPOCH !== b.EPOCH) {
|
||||
return b.EPOCH - a.EPOCH;
|
||||
}
|
||||
return b.MAX_BLOBS_PER_BLOCK - a.MAX_BLOBS_PER_BLOCK;
|
||||
});
|
||||
|
||||
for (const entry of blobSchedule) {
|
||||
if (epoch >= entry.EPOCH) {
|
||||
return entry.MAX_BLOBS_PER_BLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
// Only for testing. Should never reach this line on a public network.
|
||||
return Math.min(...blobSchedule.map((e) => e.MAX_BLOBS_PER_BLOCK));
|
||||
},
|
||||
getMaxRequestBlobSidecars(fork: ForkName): number {
|
||||
return isForkPostElectra(fork) ? config.MAX_REQUEST_BLOB_SIDECARS_ELECTRA : config.MAX_REQUEST_BLOB_SIDECARS;
|
||||
|
||||
@@ -39,8 +39,8 @@ export type ForkConfig = {
|
||||
getPostBellatrixForkTypes(slot: Slot): SSZTypesFor<ForkPostBellatrix>;
|
||||
/** Get post-deneb SSZ types by hard-fork*/
|
||||
getPostDenebForkTypes(slot: Slot): SSZTypesFor<ForkPostDeneb>;
|
||||
/** Get max blobs per block by hard-fork */
|
||||
getMaxBlobsPerBlock(fork: ForkName): number;
|
||||
/** Get max blobs per block at a given epoch */
|
||||
getMaxBlobsPerBlock(epoch: Epoch): number;
|
||||
/** Get max request blob sidecars by hard-fork */
|
||||
getMaxRequestBlobSidecars(fork: ForkName): number;
|
||||
};
|
||||
|
||||
86
packages/config/test/unit/config.test.ts
Normal file
86
packages/config/test/unit/config.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import {beforeAll, describe, expect, it} from "vitest";
|
||||
import {chainConfig} from "../../src/default.js";
|
||||
import {ChainConfig, createForkConfig} from "../../src/index.js";
|
||||
|
||||
describe("getMaxBlobsPerBlock", () => {
|
||||
let defaultConfig: ChainConfig;
|
||||
|
||||
beforeAll(() => {
|
||||
// Force tests to run on fulu fork
|
||||
// TODO Fulu: getMaxBlobsPerBlock's result is hardcoded for deneb and electra in fusaka devnet-0. So we need to define
|
||||
// defaultConfig to force tests to run on fulu to get expected result. Remove this after devnet-0.
|
||||
defaultConfig = {
|
||||
...chainConfig,
|
||||
ALTAIR_FORK_EPOCH: 0,
|
||||
BELLATRIX_FORK_EPOCH: 0,
|
||||
CAPELLA_FORK_EPOCH: 0,
|
||||
DENEB_FORK_EPOCH: 0,
|
||||
ELECTRA_FORK_EPOCH: 0,
|
||||
FULU_FORK_EPOCH: 0,
|
||||
};
|
||||
});
|
||||
|
||||
it("should throw an error if BLOB_SCHEDULE is empty", () => {
|
||||
const config = createForkConfig({...defaultConfig, BLOB_SCHEDULE: []});
|
||||
|
||||
expect(() => config.getMaxBlobsPerBlock(0)).toThrowError();
|
||||
});
|
||||
|
||||
it("should return same value for passed epochs if there is only a single BLOB_SCHEDULE entry", () => {
|
||||
const config = createForkConfig({
|
||||
...defaultConfig,
|
||||
BLOB_SCHEDULE: [{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 10}],
|
||||
});
|
||||
|
||||
expect(config.getMaxBlobsPerBlock(0)).toEqual(10);
|
||||
expect(config.getMaxBlobsPerBlock(5)).toEqual(10);
|
||||
expect(config.getMaxBlobsPerBlock(10)).toEqual(10);
|
||||
});
|
||||
|
||||
it("should select correct value for passed epoch based on BLOB_SCHEDULE thresholds", () => {
|
||||
const config = createForkConfig({
|
||||
...defaultConfig,
|
||||
BLOB_SCHEDULE: [
|
||||
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 1},
|
||||
{EPOCH: 2, MAX_BLOBS_PER_BLOCK: 2},
|
||||
{EPOCH: 4, MAX_BLOBS_PER_BLOCK: 3},
|
||||
],
|
||||
});
|
||||
|
||||
expect(config.getMaxBlobsPerBlock(0)).toEqual(1);
|
||||
expect(config.getMaxBlobsPerBlock(1)).toEqual(1);
|
||||
expect(config.getMaxBlobsPerBlock(2)).toEqual(2);
|
||||
expect(config.getMaxBlobsPerBlock(3)).toEqual(2);
|
||||
expect(config.getMaxBlobsPerBlock(4)).toEqual(3);
|
||||
expect(config.getMaxBlobsPerBlock(100)).toEqual(3);
|
||||
});
|
||||
|
||||
it("should return correct values if BLOB_SCHEDULE entries are unsorted", () => {
|
||||
const config = createForkConfig({
|
||||
...defaultConfig,
|
||||
BLOB_SCHEDULE: [
|
||||
{EPOCH: 15, MAX_BLOBS_PER_BLOCK: 1},
|
||||
{EPOCH: 5, MAX_BLOBS_PER_BLOCK: 3},
|
||||
{EPOCH: 10, MAX_BLOBS_PER_BLOCK: 5},
|
||||
],
|
||||
});
|
||||
expect(config.getMaxBlobsPerBlock(0)).toEqual(1);
|
||||
expect(config.getMaxBlobsPerBlock(6)).toEqual(3);
|
||||
expect(config.getMaxBlobsPerBlock(10)).toEqual(5);
|
||||
expect(config.getMaxBlobsPerBlock(14)).toEqual(5);
|
||||
expect(config.getMaxBlobsPerBlock(15)).toEqual(1);
|
||||
expect(config.getMaxBlobsPerBlock(16)).toEqual(1);
|
||||
});
|
||||
|
||||
it("should return minimum value if epoch is below lowest configured BLOB_SCHEDULE epoch", () => {
|
||||
const config = createForkConfig({
|
||||
...defaultConfig,
|
||||
BLOB_SCHEDULE: [
|
||||
{EPOCH: 5, MAX_BLOBS_PER_BLOCK: 3},
|
||||
{EPOCH: 10, MAX_BLOBS_PER_BLOCK: 5},
|
||||
{EPOCH: 15, MAX_BLOBS_PER_BLOCK: 2},
|
||||
],
|
||||
});
|
||||
expect(config.getMaxBlobsPerBlock(0)).toEqual(2);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import {describe, expect, it} from "vitest";
|
||||
import {chainConfig} from "../../src/default.js";
|
||||
import {chainConfigFromJson, chainConfigToJson} from "../../src/index.js";
|
||||
import {BlobSchedule, chainConfigFromJson, chainConfigToJson} from "../../src/index.js";
|
||||
|
||||
describe("chainConfig JSON", () => {
|
||||
it("Convert to and from JSON", () => {
|
||||
@@ -9,4 +9,19 @@ describe("chainConfig JSON", () => {
|
||||
|
||||
expect(chainConfigRes).toEqual(chainConfig);
|
||||
});
|
||||
|
||||
it("Custom blob schedule", () => {
|
||||
const blobSchedule: BlobSchedule = [
|
||||
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 10},
|
||||
{EPOCH: 10, MAX_BLOBS_PER_BLOCK: Infinity},
|
||||
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: 20},
|
||||
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: Infinity},
|
||||
];
|
||||
const configWithCustomBlobSchedule = {...chainConfig, BLOB_SCHEDULE: blobSchedule};
|
||||
|
||||
const json = chainConfigToJson(configWithCustomBlobSchedule);
|
||||
const chainConfigRes = chainConfigFromJson(json);
|
||||
|
||||
expect(chainConfigRes).toEqual(configWithCustomBlobSchedule);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lodestar/db",
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"description": "DB modules of Lodestar",
|
||||
"author": "ChainSafe Systems",
|
||||
"homepage": "https://github.com/ChainSafe/lodestar#readme",
|
||||
@@ -36,12 +36,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@chainsafe/ssz": "^1.2.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"classic-level": "^1.4.1",
|
||||
"it-all": "^3.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lodestar/logger": "^1.29.0"
|
||||
"@lodestar/logger": "^1.30.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lodestar/flare",
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"description": "Beacon chain debugging tool",
|
||||
"author": "ChainSafe Systems",
|
||||
"license": "Apache-2.0",
|
||||
@@ -60,12 +60,12 @@
|
||||
"dependencies": {
|
||||
"@chainsafe/bls-keygen": "^0.4.0",
|
||||
"@chainsafe/blst": "^2.2.0",
|
||||
"@lodestar/api": "^1.29.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/state-transition": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/api": "^1.30.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/state-transition": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"yargs": "^17.7.1"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
@@ -37,11 +37,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@chainsafe/ssz": "^1.2.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/state-transition": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0"
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/state-transition": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0"
|
||||
},
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
|
||||
@@ -943,6 +943,19 @@ export class ForkChoice implements IForkChoice {
|
||||
return this.protoArray.getAllNonAncestorNodes(blockRoot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns both ancestor and non-ancestor blocks in a single traversal.
|
||||
*/
|
||||
getAllAncestorAndNonAncestorBlocks(blockRoot: RootHex): {ancestors: ProtoBlock[]; nonAncestors: ProtoBlock[]} {
|
||||
const {ancestors, nonAncestors} = this.protoArray.getAllAncestorAndNonAncestorNodes(blockRoot);
|
||||
|
||||
return {
|
||||
// the last node is the previous finalized one, it's there to check onBlock finalized checkpoint only.
|
||||
ancestors: ancestors.slice(0, ancestors.length - 1),
|
||||
nonAncestors,
|
||||
};
|
||||
}
|
||||
|
||||
getCanonicalBlockAtSlot(slot: Slot): ProtoBlock | null {
|
||||
if (slot > this.head.slot) {
|
||||
return null;
|
||||
|
||||
@@ -217,6 +217,10 @@ export interface IForkChoice {
|
||||
* The same to iterateAncestorBlocks but this gets non-ancestor nodes instead of ancestor nodes.
|
||||
*/
|
||||
getAllNonAncestorBlocks(blockRoot: RootHex): ProtoBlock[];
|
||||
/**
|
||||
* Returns both ancestor and non-ancestor blocks in a single traversal.
|
||||
*/
|
||||
getAllAncestorAndNonAncestorBlocks(blockRoot: RootHex): {ancestors: ProtoBlock[]; nonAncestors: ProtoBlock[]};
|
||||
getCanonicalBlockAtSlot(slot: Slot): ProtoBlock | null;
|
||||
getCanonicalBlockClosestLteSlot(slot: Slot): ProtoBlock | null;
|
||||
/**
|
||||
|
||||
@@ -903,6 +903,44 @@ export class ProtoArray {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns both ancestor and non-ancestor nodes in a single traversal.
|
||||
*/
|
||||
getAllAncestorAndNonAncestorNodes(blockRoot: RootHex): {ancestors: ProtoNode[]; nonAncestors: ProtoNode[]} {
|
||||
const startIndex = this.indices.get(blockRoot);
|
||||
if (startIndex === undefined) {
|
||||
return {ancestors: [], nonAncestors: []};
|
||||
}
|
||||
|
||||
let node = this.nodes[startIndex];
|
||||
if (node === undefined) {
|
||||
throw new ProtoArrayError({
|
||||
code: ProtoArrayErrorCode.INVALID_NODE_INDEX,
|
||||
index: startIndex,
|
||||
});
|
||||
}
|
||||
|
||||
const ancestors: ProtoNode[] = [];
|
||||
const nonAncestors: ProtoNode[] = [];
|
||||
|
||||
let nodeIndex = startIndex;
|
||||
while (node.parent !== undefined) {
|
||||
ancestors.push(node);
|
||||
|
||||
const parentIndex = node.parent;
|
||||
node = this.getNodeFromIndex(parentIndex);
|
||||
|
||||
// Nodes between nodeIndex and parentIndex are non-ancestor nodes
|
||||
nonAncestors.push(...this.getNodesBetween(nodeIndex, parentIndex));
|
||||
nodeIndex = parentIndex;
|
||||
}
|
||||
|
||||
ancestors.push(node);
|
||||
nonAncestors.push(...this.getNodesBetween(nodeIndex, 0));
|
||||
|
||||
return {ancestors, nonAncestors};
|
||||
}
|
||||
|
||||
hasBlock(blockRoot: RootHex): boolean {
|
||||
return this.indices.has(blockRoot);
|
||||
}
|
||||
|
||||
@@ -126,6 +126,38 @@ describe("Forkchoice", () => {
|
||||
expect(summaries[0]).toEqual({...block, bestChild: undefined, bestDescendant: undefined, parent: 0, weight: 0});
|
||||
});
|
||||
|
||||
it("getAllAncestorAndNonAncestorBlocks equals getAllAncestorBlocks + getAllNonAncestorBlocks", () => {
|
||||
// Create a simple chain: 0 -> 1 -> 2 -> 3
|
||||
populateProtoArray(genesisSlot + 3);
|
||||
|
||||
// Create a fork by adding block 10 with parent at genesis
|
||||
const forkBlock = {
|
||||
...getBlock(genesisSlot + 10),
|
||||
parentRoot: finalizedRoot, // Connect directly to genesis
|
||||
};
|
||||
protoArr.onBlock(forkBlock, forkBlock.slot);
|
||||
|
||||
const forkchoice = new ForkChoice(config, fcStore, protoArr);
|
||||
|
||||
// Test with a block from the canonical chain
|
||||
const canonicalBlockRoot = getBlockRoot(genesisSlot + 3);
|
||||
const canonicalAncestorBlocks = forkchoice.getAllAncestorBlocks(canonicalBlockRoot);
|
||||
const canonicalNonAncestorBlocks = forkchoice.getAllNonAncestorBlocks(canonicalBlockRoot);
|
||||
const canonicalCombined = forkchoice.getAllAncestorAndNonAncestorBlocks(canonicalBlockRoot);
|
||||
|
||||
expect(canonicalCombined.ancestors).toEqual(canonicalAncestorBlocks);
|
||||
expect(canonicalCombined.nonAncestors).toEqual(canonicalNonAncestorBlocks);
|
||||
|
||||
// Test with a block from the fork chain
|
||||
const forkBlockRoot = getBlockRoot(genesisSlot + 10);
|
||||
const forkAncestorBlocks = forkchoice.getAllAncestorBlocks(forkBlockRoot);
|
||||
const forkNonAncestorBlocks = forkchoice.getAllNonAncestorBlocks(forkBlockRoot);
|
||||
const forkCombined = forkchoice.getAllAncestorAndNonAncestorBlocks(forkBlockRoot);
|
||||
|
||||
expect(forkCombined.ancestors).toEqual(forkAncestorBlocks);
|
||||
expect(forkCombined.nonAncestors).toEqual(forkNonAncestorBlocks);
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
expect(SLOTS_PER_EPOCH).toBe(32);
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -74,11 +74,11 @@
|
||||
"@chainsafe/blst": "^0.2.0",
|
||||
"@chainsafe/persistent-merkle-tree": "^1.1.0",
|
||||
"@chainsafe/ssz": "^1.2.0",
|
||||
"@lodestar/api": "^1.29.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/api": "^1.30.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"mitt": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src", "test"],
|
||||
"exclude": ["src/index.browser.ts", "test/unit/webEsmBundle.browser.test.ts"]
|
||||
// These files to be excluded in case we run `check-types` before `build:bundle`
|
||||
"exclude": ["test/unit/webEsmBundle.browser.test.ts", "test/browser/webEsmBundle.browser.test.ts"]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -63,14 +63,14 @@
|
||||
},
|
||||
"types": "lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"winston": "^3.8.2",
|
||||
"winston-daily-rotate-file": "^4.7.1",
|
||||
"winston-transport": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chainsafe/threads": "^1.11.1",
|
||||
"@lodestar/test-utils": "^1.29.0",
|
||||
"@lodestar/test-utils": "^1.30.0",
|
||||
"@types/triple-beam": "^1.3.2",
|
||||
"triple-beam": "^1.3.0"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lodestar/params",
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"description": "Chain parameters required for lodestar",
|
||||
"author": "ChainSafe Systems",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -66,13 +66,13 @@
|
||||
"@ethereumjs/tx": "^4.1.2",
|
||||
"@ethereumjs/util": "^8.0.6",
|
||||
"@ethereumjs/vm": "^6.4.2",
|
||||
"@lodestar/api": "^1.29.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/light-client": "^1.29.0",
|
||||
"@lodestar/logger": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/api": "^1.30.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/light-client": "^1.30.0",
|
||||
"@lodestar/logger": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"ethereum-cryptography": "^2.0.0",
|
||||
"find-up": "^6.3.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
@@ -81,7 +81,7 @@
|
||||
"yargs": "^17.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lodestar/test-utils": "^1.29.0",
|
||||
"@lodestar/test-utils": "^1.30.0",
|
||||
"@types/http-proxy": "^1.17.10",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"axios": "^1.3.4",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -54,9 +54,9 @@
|
||||
"dependencies": {
|
||||
"@chainsafe/fast-crc32c": "^4.2.0",
|
||||
"@libp2p/interface": "^2.7.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"it-all": "^3.0.4",
|
||||
"it-pipe": "^3.0.1",
|
||||
"snappy": "^7.2.2",
|
||||
@@ -65,8 +65,8 @@
|
||||
"uint8arraylist": "^2.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lodestar/logger": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/logger": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"libp2p": "2.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lodestar/spec-test-util",
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"description": "Spec test suite generator from yaml test files",
|
||||
"author": "ChainSafe Systems",
|
||||
"license": "Apache-2.0",
|
||||
@@ -62,15 +62,11 @@
|
||||
"blockchain"
|
||||
],
|
||||
"dependencies": {
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"rimraf": "^4.4.1",
|
||||
"snappyjs": "^0.7.0",
|
||||
"tar": "^6.1.13",
|
||||
"vitest": "^3.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/tar": "^6.1.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vitest": "^3.0.6"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -65,10 +65,10 @@
|
||||
"@chainsafe/pubkey-index-map": "^3.0.0",
|
||||
"@chainsafe/ssz": "^1.2.0",
|
||||
"@chainsafe/swap-or-not-shuffle": "^1.2.1",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"bigint-buffer": "^1.1.5"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
@@ -3,7 +3,7 @@ import {Attestation, Slot, electra, phase0, ssz} from "@lodestar/types";
|
||||
import {toRootHex} from "@lodestar/utils";
|
||||
import {assert} from "@lodestar/utils";
|
||||
import {CachedBeaconStateAllForks, CachedBeaconStatePhase0} from "../types.js";
|
||||
import {computeEpochAtSlot} from "../util/index.js";
|
||||
import {computeEndSlotAtEpoch, computeEpochAtSlot} from "../util/index.js";
|
||||
import {isValidIndexedAttestation} from "./index.js";
|
||||
|
||||
/**
|
||||
@@ -78,9 +78,11 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo
|
||||
|
||||
// post deneb, the attestations are valid till end of next epoch
|
||||
if (!(data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= slot && isTimelyTarget(fork, slot - data.slot))) {
|
||||
const windowStart = data.slot + MIN_ATTESTATION_INCLUSION_DELAY;
|
||||
const windowEnd = fork >= ForkSeq.deneb ? computeEndSlotAtEpoch(computedEpoch + 1) : data.slot + SLOTS_PER_EPOCH;
|
||||
|
||||
throw new Error(
|
||||
"Attestation slot not within inclusion window: " +
|
||||
`slot=${data.slot} window=${data.slot + MIN_ATTESTATION_INCLUSION_DELAY}..${data.slot + SLOTS_PER_EPOCH}`
|
||||
`Attestation slot not within inclusion window: slot=${data.slot} window=${windowStart}..${windowEnd}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
getFullOrBlindedPayloadFromBody,
|
||||
isMergeTransitionComplete,
|
||||
} from "../util/execution.js";
|
||||
import {getRandaoMix} from "../util/index.js";
|
||||
import {computeEpochAtSlot, getRandaoMix} from "../util/index.js";
|
||||
import {BlockExternalData, ExecutionPayloadStatus} from "./externalData.js";
|
||||
|
||||
export function processExecutionPayload(
|
||||
@@ -49,7 +49,7 @@ export function processExecutionPayload(
|
||||
}
|
||||
|
||||
if (isForkPostDeneb(forkName)) {
|
||||
const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(forkName);
|
||||
const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(computeEpochAtSlot(state.slot));
|
||||
const blobKzgCommitmentsLen = (body as deneb.BeaconBlockBody).blobKzgCommitments?.length ?? 0;
|
||||
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
|
||||
throw Error(`blobKzgCommitmentsLen of ${blobKzgCommitmentsLen} exceeds limit=${maxBlobsPerBlock}`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@lodestar/test-utils",
|
||||
"private": true,
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"description": "Test utilities reused across other packages",
|
||||
"author": "ChainSafe Systems",
|
||||
"license": "Apache-2.0",
|
||||
@@ -59,8 +59,8 @@
|
||||
"dependencies": {
|
||||
"@chainsafe/bls-keystore": "^3.1.0",
|
||||
"@chainsafe/blst": "^2.2.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"axios": "^1.3.4",
|
||||
"testcontainers": "^10.2.1",
|
||||
"tmp": "^0.2.1",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -69,7 +69,7 @@
|
||||
"types": "lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@chainsafe/ssz": "^1.2.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"ethereum-cryptography": "^2.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/ChainSafe/lodestar/issues"
|
||||
},
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"type": "module",
|
||||
"exports": "./lib/index.js",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lodestar/validator",
|
||||
"version": "1.29.0",
|
||||
"version": "1.30.0",
|
||||
"description": "A Typescript implementation of the validator client",
|
||||
"author": "ChainSafe Systems",
|
||||
"license": "LGPL-3.0",
|
||||
@@ -47,17 +47,17 @@
|
||||
"dependencies": {
|
||||
"@chainsafe/blst": "^2.2.0",
|
||||
"@chainsafe/ssz": "^1.2.0",
|
||||
"@lodestar/api": "^1.29.0",
|
||||
"@lodestar/config": "^1.29.0",
|
||||
"@lodestar/db": "^1.29.0",
|
||||
"@lodestar/params": "^1.29.0",
|
||||
"@lodestar/state-transition": "^1.29.0",
|
||||
"@lodestar/types": "^1.29.0",
|
||||
"@lodestar/utils": "^1.29.0",
|
||||
"@lodestar/api": "^1.30.0",
|
||||
"@lodestar/config": "^1.30.0",
|
||||
"@lodestar/db": "^1.30.0",
|
||||
"@lodestar/params": "^1.30.0",
|
||||
"@lodestar/state-transition": "^1.30.0",
|
||||
"@lodestar/types": "^1.30.0",
|
||||
"@lodestar/utils": "^1.30.0",
|
||||
"strict-event-emitter-types": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@lodestar/test-utils": "^1.29.0",
|
||||
"@lodestar/test-utils": "^1.30.0",
|
||||
"bigint-buffer": "^1.1.5",
|
||||
"rimraf": "^4.4.1"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ChainConfig, chainConfigToJson} from "@lodestar/config";
|
||||
import {ChainConfig, SpecJson, chainConfigToJson} from "@lodestar/config";
|
||||
import {BeaconPreset, activePreset, presetToJson} from "@lodestar/params";
|
||||
|
||||
export class NotEqualParamsError extends Error {}
|
||||
@@ -20,7 +20,7 @@ type ConfigWithPreset = ChainConfig & BeaconPreset;
|
||||
* So this check only compares a specific list of parameters that are consensus critical, ignoring the rest. Typed
|
||||
* config and preset ensure new parameters are labeled critical or ignore, facilitating maintenance of the list.
|
||||
*/
|
||||
export function assertEqualParams(localConfig: ChainConfig, externalSpecJson: Record<string, string>): void {
|
||||
export function assertEqualParams(localConfig: ChainConfig, externalSpecJson: SpecJson): void {
|
||||
// Before comparing, add preset which is bundled in api impl config route.
|
||||
// config and preset must be serialized to JSON for safe comparisions.
|
||||
const localSpecJson = {
|
||||
@@ -259,6 +259,6 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record<keyof ConfigWit
|
||||
NODE_CUSTODY_REQUIREMENT: false,
|
||||
VALIDATOR_CUSTODY_REQUIREMENT: fuluForkRelevant,
|
||||
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: fuluForkRelevant,
|
||||
MAX_BLOBS_PER_BLOCK_FULU: fuluForkRelevant,
|
||||
BLOB_SCHEDULE: fuluForkRelevant,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12095,7 +12095,7 @@ tar@6.1.11:
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
||||
tar@^6.0.2, tar@^6.1.11, tar@^6.1.13, tar@^6.1.2:
|
||||
tar@^6.0.2, tar@^6.1.11, tar@^6.1.2:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
|
||||
integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
|
||||
|
||||
Reference in New Issue
Block a user