Skip to content

Commit cea22ab

Browse files
author
bussyjd
committed
Merge remote-tracking branch 'origin/pr/561' into release/v0.10.0-rc7
2 parents 61f0cbb + ed2e170 commit cea22ab

7 files changed

Lines changed: 554 additions & 62 deletions

File tree

internal/embed/networks/ethereum/helmfile.yaml.gotmpl

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ releases:
1616
executionClient: '{{ .Values.executionClient }}'
1717
consensusClient: '{{ .Values.consensusClient }}'
1818
network: '{{ .Values.network }}'
19+
mode: '{{ .Values.mode }}'
20+
executionStorageSize: '{{ index .Values "executionStorageSize" | default "" }}'
21+
consensusStorageSize: '{{ index .Values "consensusStorageSize" | default "" }}'
1922

2023
# Ethereum node (execution and consensus clients)
2124
# Uses the external ethereum-helm-charts/ethereum-node umbrella chart
@@ -58,24 +61,23 @@ releases:
5861
{{- end }}
5962
{{- if ne .Values.mode "archive" }}
6063
- --full
61-
{{- else if eq (.Values.pruneKind | default "") "before" }}
62-
# Partial archive: prune state history before block N.
63-
# Receipts/bodies are pruned to the same cutoff via the
64-
# pre-merge presets where applicable; otherwise reth
65-
# uses the same `before` value.
64+
{{- else if eq (index .Values "pruneKind" | default "") "before" }}
65+
# Partial archive: keep state, receipts and bodies from
66+
# the requested block forward. Reth v2.2.0 and current
67+
# main both support exact .before cutoffs for these
68+
# segments, so avoid the coarser pre-merge preset.
6669
- --prune.account-history.before={{ .Values.pruneBlock }}
6770
- --prune.storage-history.before={{ .Values.pruneBlock }}
68-
{{- if le (int .Values.pruneBlock) 15537394 }}
69-
- --prune.receipts.pre-merge
70-
- --prune.bodies.pre-merge
71-
{{- else }}
7271
- --prune.receipts.before={{ .Values.pruneBlock }}
7372
- --prune.bodies.before={{ .Values.pruneBlock }}
74-
{{- end }}
75-
{{- else if eq (.Values.pruneKind | default "") "distance" }}
76-
# Partial archive: keep last N blocks of history.
73+
{{- else if eq (index .Values "pruneKind" | default "") "distance" }}
74+
# Partial archive: keep last N blocks of state, receipts
75+
# and bodies. Reth interprets .distance as anchored to
76+
# the chain tip at run time.
7777
- --prune.account-history.distance={{ .Values.pruneDistance }}
7878
- --prune.storage-history.distance={{ .Values.pruneDistance }}
79+
- --prune.receipts.distance={{ .Values.pruneDistance }}
80+
- --prune.bodies.distance={{ .Values.pruneDistance }}
7981
{{- end }}
8082
{{- end }}
8183

@@ -105,7 +107,7 @@ releases:
105107
{{- end }}
106108
persistence:
107109
enabled: true
108-
size: {{ if eq .Values.network "mainnet" }}{{ if eq .Values.mode "archive" }}4500Gi{{ else }}500Gi{{ end }}{{ else }}{{ if eq .Values.mode "archive" }}300Gi{{ else }}100Gi{{ end }}{{ end }}
110+
size: {{ if (index .Values "executionStorageSize" | default "") }}{{ index .Values "executionStorageSize" }}{{ else }}{{ if eq .Values.network "mainnet" }}{{ if eq .Values.mode "archive" }}4500Gi{{ else }}500Gi{{ end }}{{ else }}{{ if eq .Values.mode "archive" }}300Gi{{ else }}100Gi{{ end }}{{ end }}{{ end }}
109111
existingClaim: execution-{{ .Values.executionClient }}-{{ .Values.network }}
110112

111113
# Consensus client (pinned versions — Renovate-tracked)
@@ -131,7 +133,7 @@ releases:
131133
{{- end }}
132134
persistence:
133135
enabled: true
134-
size: {{ if eq .Values.network "mainnet" }}{{ if eq .Values.mode "archive" }}500Gi{{ else }}200Gi{{ end }}{{ else }}{{ if eq .Values.mode "archive" }}100Gi{{ else }}50Gi{{ end }}{{ end }}
136+
size: {{ if (index .Values "consensusStorageSize" | default "") }}{{ index .Values "consensusStorageSize" }}{{ else }}{{ if eq .Values.network "mainnet" }}{{ if eq .Values.mode "archive" }}500Gi{{ else }}200Gi{{ end }}{{ else }}{{ if eq .Values.mode "archive" }}100Gi{{ else }}50Gi{{ end }}{{ end }}{{ end }}
135137
existingClaim: consensus-{{ .Values.consensusClient }}-{{ .Values.network }}
136138

137139
# Metadata ConfigMap for frontend discovery

internal/embed/networks/ethereum/templates/pvc.yaml

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
{{- if eq .Release.Name "ethereum-pvcs" }}
22
{{- /*
3-
PVC sizing is a function of (network, mode). Sizes are estimates with
4-
~30% headroom for chain growth. local-path storage does not pre-allocate,
5-
so these requests primarily document intent and serve as soft caps when
6-
a sized storage class is swapped in later.
3+
PVC sizing is resolved by the CLI and written into values.yaml so partial
4+
archive scopes get smaller requests than full genesis archives. The fallback
5+
keeps older generated values usable.
76
*/ -}}
87
{{- $mode := default "full" .Values.mode -}}
98
{{- $isArchive := eq $mode "archive" -}}
10-
{{- $execSize := "500Gi" -}}
11-
{{- $consensusSize := "200Gi" -}}
12-
{{- if eq .Values.network "mainnet" -}}
13-
{{- if $isArchive -}}
14-
{{- $execSize = "4500Gi" -}}
15-
{{- $consensusSize = "500Gi" -}}
9+
{{- $execSize := index .Values "executionStorageSize" | default "" -}}
10+
{{- $consensusSize := index .Values "consensusStorageSize" | default "" -}}
11+
{{- if or (eq $execSize "") (eq $consensusSize "") -}}
12+
{{- $execSize = "500Gi" -}}
13+
{{- $consensusSize = "200Gi" -}}
14+
{{- if eq .Values.network "mainnet" -}}
15+
{{- if $isArchive -}}
16+
{{- $execSize = "4500Gi" -}}
17+
{{- $consensusSize = "500Gi" -}}
18+
{{- end -}}
1619
{{- else -}}
17-
{{- $execSize = "500Gi" -}}
18-
{{- $consensusSize = "200Gi" -}}
19-
{{- end -}}
20-
{{- else -}}
21-
{{- /* sepolia, hoodi and other testnets */ -}}
22-
{{- if $isArchive -}}
23-
{{- $execSize = "300Gi" -}}
24-
{{- $consensusSize = "100Gi" -}}
25-
{{- else -}}
26-
{{- $execSize = "100Gi" -}}
27-
{{- $consensusSize = "50Gi" -}}
20+
{{- /* sepolia, hoodi and other testnets */ -}}
21+
{{- if $isArchive -}}
22+
{{- $execSize = "300Gi" -}}
23+
{{- $consensusSize = "100Gi" -}}
24+
{{- else -}}
25+
{{- $execSize = "100Gi" -}}
26+
{{- $consensusSize = "50Gi" -}}
27+
{{- end -}}
2828
{{- end -}}
2929
{{- end -}}
3030
---

internal/network/network.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ func Install(cfg *config.Config, u *ui.UI, network string, overrides map[string]
148148
if modeValue == "" {
149149
modeValue = "full"
150150
}
151-
if err := CheckNetworkDiskSpace(u, cfg.DataDir, netValue, modeValue); err != nil {
151+
executionClient := templateData["ExecutionClient"]
152+
if err := CheckNetworkDiskSpace(u, cfg.DataDir, netValue, modeValue, executionClient, archiveScope); err != nil {
152153
return err
153154
}
154155
}
@@ -176,7 +177,7 @@ func Install(cfg *config.Config, u *ui.UI, network string, overrides map[string]
176177
if network == "ethereum" {
177178
var sb strings.Builder
178179
sb.Write(buf.Bytes())
179-
appendArchiveScopeYAML(&sb, archiveScope)
180+
appendArchiveScopeYAML(&sb, templateData["Network"], templateData["Mode"], templateData["ExecutionClient"], archiveScope)
180181
buf.Reset()
181182
buf.WriteString(sb.String())
182183
}

internal/network/picker.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ func promptCustomBlock(u *ui.UI) (ArchiveScope, error) {
214214
// appendArchiveScopeYAML serializes the resolved scope into a YAML
215215
// fragment for values.yaml, in a form that helmfile reads at deploy time
216216
// to emit per-client prune args.
217-
func appendArchiveScopeYAML(b *strings.Builder, scope ArchiveScope) {
217+
func appendArchiveScopeYAML(b *strings.Builder, network, mode, executionClient string, scope ArchiveScope) {
218+
profile := resolveEthereumStorageProfile(network, mode, executionClient, scope)
219+
218220
b.WriteString("\n# Pruning scope, resolved by `obol network install` from --mode/--since.\n")
219221
b.WriteString("# Edit via the CLI flags rather than this file; helmfile reads these\n")
220222
b.WriteString("# verbatim and emits client-specific prune args at deploy time.\n")
@@ -233,4 +235,9 @@ func appendArchiveScopeYAML(b *strings.Builder, scope ArchiveScope) {
233235
case "distance":
234236
fmt.Fprintf(b, "pruneKind: \"distance\"\npruneBlock: 0\npruneDistance: %d\n", scope.Distance)
235237
}
238+
239+
b.WriteString("\n# Storage profile derived from the resolved archive scope.\n")
240+
fmt.Fprintf(b, "executionStorageSize: %s\n", profile.ExecutionSize)
241+
fmt.Fprintf(b, "consensusStorageSize: %s\n", profile.ConsensusSize)
242+
fmt.Fprintf(b, "diskRequirementGB: %d\n", profile.DiskRequirementGB)
236243
}

internal/network/preflight.go

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,6 @@ import (
77
"github.com/ObolNetwork/obol-stack/internal/ui"
88
)
99

10-
// diskSpaceRequirementGB returns the recommended free-disk minimum for
11-
// (network, mode) in gigabytes. Numbers include ~30% headroom for chain
12-
// growth between releases. Sizes are reth-anchored; other clients are in
13-
// the same ballpark.
14-
func diskSpaceRequirementGB(network, mode string) uint64 {
15-
archive := mode == "archive"
16-
switch network {
17-
case "mainnet":
18-
if archive {
19-
return 5000
20-
}
21-
return 700
22-
default:
23-
// sepolia, hoodi, and other testnets
24-
if archive {
25-
return 400
26-
}
27-
return 150
28-
}
29-
}
30-
3110
// freeDiskBytes returns the free disk bytes available at path. Used to
3211
// check whether a network install has room to grow before we let helmfile
3312
// schedule a 4TB PVC that will silently fill the host overnight.
@@ -46,8 +25,9 @@ func freeDiskBytes(path string) (uint64, error) {
4625
// in non-interactive contexts (no TTY, JSON mode) the prompt auto-accepts
4726
// so scripted installs don't deadlock. The user only blocks the install by
4827
// explicitly declining at an interactive prompt.
49-
func CheckNetworkDiskSpace(u *ui.UI, dataDir, network, mode string) error {
50-
requiredGB := diskSpaceRequirementGB(network, mode)
28+
func CheckNetworkDiskSpace(u *ui.UI, dataDir, network, mode, executionClient string, scope ArchiveScope) error {
29+
profile := resolveEthereumStorageProfile(network, mode, executionClient, scope)
30+
requiredGB := profile.DiskRequirementGB
5131

5232
freeBytes, err := freeDiskBytes(dataDir)
5333
if err != nil {
@@ -58,7 +38,7 @@ func CheckNetworkDiskSpace(u *ui.UI, dataDir, network, mode string) error {
5838

5939
freeGB := freeBytes / (1024 * 1024 * 1024)
6040

61-
u.Detail("Disk space", fmt.Sprintf("%d GB free at %s (this network needs ~%d GB)", freeGB, dataDir, requiredGB))
41+
u.Detail("Disk space", fmt.Sprintf("%d GB free at %s (this network needs ~%d GB for %s)", freeGB, dataDir, requiredGB, profile.Label))
6242

6343
if freeGB >= requiredGB {
6444
return nil
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package network
2+
3+
import (
4+
"fmt"
5+
"math"
6+
)
7+
8+
// ethereumStorageProfile is the single source of truth for Ethereum local
9+
// node storage sizing. The profile is intentionally conservative because
10+
// local-path storage does not reserve bytes up front, but sized storage
11+
// classes do enforce these requests.
12+
type ethereumStorageProfile struct {
13+
ExecutionSize string
14+
ConsensusSize string
15+
DiskRequirementGB uint64
16+
Label string
17+
}
18+
19+
func resolveEthereumStorageProfile(network, mode, executionClient string, scope ArchiveScope) ethereumStorageProfile {
20+
if mode != "archive" {
21+
if network == "mainnet" {
22+
return ethereumStorageProfile{
23+
ExecutionSize: "500Gi",
24+
ConsensusSize: "200Gi",
25+
DiskRequirementGB: 700,
26+
Label: "full mainnet",
27+
}
28+
}
29+
return ethereumStorageProfile{
30+
ExecutionSize: "100Gi",
31+
ConsensusSize: "50Gi",
32+
DiskRequirementGB: 150,
33+
Label: "full testnet",
34+
}
35+
}
36+
37+
if network != "mainnet" {
38+
return ethereumStorageProfile{
39+
ExecutionSize: "300Gi",
40+
ConsensusSize: "100Gi",
41+
DiskRequirementGB: 400,
42+
Label: "archive testnet",
43+
}
44+
}
45+
46+
if !partialArchiveClients[executionClient] || scope.Kind == "" || scope.Kind == "all" {
47+
return ethereumStorageProfile{
48+
ExecutionSize: "4500Gi",
49+
ConsensusSize: "500Gi",
50+
DiskRequirementGB: 5000,
51+
Label: "archive mainnet from genesis",
52+
}
53+
}
54+
55+
switch scope.Kind {
56+
case "before":
57+
if hf := hardforkProfileForBlock(scope.Block); hf != nil {
58+
execGi := roundUpGiB(uint64(math.Ceil(hf.ApproxArchiveSizeTB * 1024 * 1.2)))
59+
return ethereumStorageProfile{
60+
ExecutionSize: formatGi(execGi),
61+
ConsensusSize: "500Gi",
62+
DiskRequirementGB: execGi + 700,
63+
Label: "partial archive mainnet " + hf.Name,
64+
}
65+
}
66+
67+
// A raw block before the oldest known partial-archive preset could
68+
// retain almost all history, so size it as a full archive.
69+
return ethereumStorageProfile{
70+
ExecutionSize: "4500Gi",
71+
ConsensusSize: "500Gi",
72+
DiskRequirementGB: 5000,
73+
Label: "custom archive mainnet block",
74+
}
75+
case "distance":
76+
execGi := executionGiForDistance(scope.Distance)
77+
return ethereumStorageProfile{
78+
ExecutionSize: formatGi(execGi),
79+
ConsensusSize: "500Gi",
80+
DiskRequirementGB: diskRequirementForMainnetArchive(execGi),
81+
Label: "partial archive mainnet distance",
82+
}
83+
default:
84+
return ethereumStorageProfile{
85+
ExecutionSize: "4500Gi",
86+
ConsensusSize: "500Gi",
87+
DiskRequirementGB: 5000,
88+
Label: "archive mainnet",
89+
}
90+
}
91+
}
92+
93+
func hardforkProfileForBlock(block uint64) *Hardfork {
94+
var matched *Hardfork
95+
for i := range MainnetHardforks {
96+
if MainnetHardforks[i].Block <= block {
97+
matched = &MainnetHardforks[i]
98+
continue
99+
}
100+
break
101+
}
102+
return matched
103+
}
104+
105+
func diskRequirementForMainnetArchive(execGi uint64) uint64 {
106+
if execGi >= 4500 {
107+
return 5000
108+
}
109+
return execGi + 700
110+
}
111+
112+
func executionGiForDistance(distance uint64) uint64 {
113+
days := float64(distance*12) / float64(24*60*60)
114+
execGi := uint64(math.Ceil(500 + days*0.82))
115+
if execGi < 500 {
116+
execGi = 500
117+
}
118+
if execGi > 4500 {
119+
execGi = 4500
120+
}
121+
return roundUpGiB(execGi)
122+
}
123+
124+
func roundUpGiB(gib uint64) uint64 {
125+
const step = 100
126+
if gib%step == 0 {
127+
return gib
128+
}
129+
return ((gib / step) + 1) * step
130+
}
131+
132+
func formatGi(gib uint64) string {
133+
return fmt.Sprintf("%dGi", gib)
134+
}

0 commit comments

Comments
 (0)