Skip to content

Commit a087c7d

Browse files
Владислав ПоплавскийВладислав Поплавский
authored andcommitted
eth, internal/ethapi: add eth_capabilities RPC method
1 parent 82fad31 commit a087c7d

File tree

7 files changed

+547
-1
lines changed

7 files changed

+547
-1
lines changed

eth/api_backend.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"github.com/ethereum/go-ethereum/eth/tracers"
4141
"github.com/ethereum/go-ethereum/ethdb"
4242
"github.com/ethereum/go-ethereum/event"
43+
"github.com/ethereum/go-ethereum/internal/ethapi"
4344
"github.com/ethereum/go-ethereum/params"
4445
"github.com/ethereum/go-ethereum/rpc"
4546
)
@@ -278,6 +279,17 @@ func (b *EthAPIBackend) HistoryPruningCutoff() uint64 {
278279
return bn
279280
}
280281

282+
func (b *EthAPIBackend) HistoryRetention() ethapi.HistoryRetention {
283+
cfg := b.eth.config
284+
return ethapi.HistoryRetention{
285+
TxIndexHistory: cfg.TransactionHistory,
286+
LogIndexHistory: cfg.LogHistory,
287+
LogIndexDisabled: cfg.LogNoHistory,
288+
StateHistory: cfg.StateHistory,
289+
StateScheme: b.eth.blockchain.TrieDB().Scheme(),
290+
}
291+
}
292+
281293
func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
282294
return b.eth.blockchain.GetReceiptsByHash(hash), nil
283295
}

internal/ethapi/api_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,9 @@ func (b testBackend) HistoryPruningCutoff() uint64 {
708708
bn, _ := b.chain.HistoryPruningCutoff()
709709
return bn
710710
}
711+
func (b testBackend) HistoryRetention() HistoryRetention {
712+
return HistoryRetention{StateScheme: b.chain.TrieDB().Scheme()}
713+
}
711714

712715
func TestEstimateGas(t *testing.T) {
713716
t.Parallel()

internal/ethapi/backend.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ type Backend interface {
9090
ChainConfig() *params.ChainConfig
9191
Engine() consensus.Engine
9292
HistoryPruningCutoff() uint64
93+
HistoryRetention() HistoryRetention
9394

9495
// This is copied from filters.Backend
9596
// eth/filters needs to be initialized from this backend type, so methods needed by

internal/ethapi/capabilities.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Copyright 2026 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package ethapi
18+
19+
import (
20+
"github.com/ethereum/go-ethereum/common"
21+
"github.com/ethereum/go-ethereum/common/hexutil"
22+
"github.com/ethereum/go-ethereum/core/rawdb"
23+
)
24+
25+
// HistoryRetention reports a node's configured history retention windows.
26+
// It is consumed by the eth_capabilities RPC method to derive the response
27+
// described in https://github.com/ethereum/execution-apis/pull/755.
28+
type HistoryRetention struct {
29+
// TxIndexHistory is the number of recent blocks for which the
30+
// transaction lookup index is maintained. Zero means the index covers
31+
// the entire available chain.
32+
TxIndexHistory uint64
33+
34+
// LogIndexHistory is the number of recent blocks for which the log
35+
// search index is maintained. Zero means the index covers the entire
36+
// available chain.
37+
LogIndexHistory uint64
38+
39+
// LogIndexDisabled reports whether the log search index has been
40+
// turned off entirely.
41+
LogIndexDisabled bool
42+
43+
// StateHistory is the number of recent blocks for which historical
44+
// state is retained. Zero means the entire chain state is kept
45+
// (archive node).
46+
StateHistory uint64
47+
48+
// StateScheme is the state storage scheme in use, either "hash" or
49+
// "path". The hash scheme does not support a sliding state window.
50+
StateScheme string
51+
}
52+
53+
// Capabilities reports which historical data the node can serve. It is
54+
// returned by the eth_capabilities RPC method as defined in
55+
// https://github.com/ethereum/execution-apis/pull/755.
56+
type Capabilities struct {
57+
Head CapabilityHead `json:"head"`
58+
State CapabilityResource `json:"state"`
59+
Tx CapabilityResource `json:"tx"`
60+
Logs CapabilityResource `json:"logs"`
61+
Receipts CapabilityResource `json:"receipts"`
62+
Blocks CapabilityResource `json:"blocks"`
63+
StateProofs CapabilityResource `json:"stateproofs"`
64+
}
65+
66+
// CapabilityHead is the current canonical head as reported by the node.
67+
type CapabilityHead struct {
68+
BlockNumber hexutil.Uint64 `json:"blockNumber"`
69+
BlockHash common.Hash `json:"blockHash"`
70+
}
71+
72+
// CapabilityResource describes the availability of a single data resource.
73+
type CapabilityResource struct {
74+
Disabled bool `json:"disabled"`
75+
OldestBlock hexutil.Uint64 `json:"oldestBlock"`
76+
DeleteStrategy DeleteStrategy `json:"deleteStrategy"`
77+
}
78+
79+
// DeleteStrategy describes how data of a resource is removed over time.
80+
//
81+
// Two strategies are defined by the spec:
82+
//
83+
// - "none": data is never deleted; the resource is permanently
84+
// retained from oldestBlock onwards.
85+
// - "window": data is retained for a sliding window of the most recent
86+
// RetentionBlocks blocks.
87+
//
88+
// RetentionBlocks is omitted from the JSON output for the "none" strategy.
89+
type DeleteStrategy struct {
90+
Type string `json:"type"`
91+
RetentionBlocks *uint64 `json:"retentionBlocks,omitempty"`
92+
}
93+
94+
// strategyNone returns a DeleteStrategy with type "none".
95+
func strategyNone() DeleteStrategy {
96+
return DeleteStrategy{Type: "none"}
97+
}
98+
99+
// strategyWindow returns a DeleteStrategy with type "window" and the given
100+
// retention block count.
101+
func strategyWindow(retention uint64) DeleteStrategy {
102+
return DeleteStrategy{Type: "window", RetentionBlocks: &retention}
103+
}
104+
105+
// Capabilities implements the eth_capabilities RPC method as defined in
106+
// https://github.com/ethereum/execution-apis/pull/755. It returns a
107+
// description of the historical data this node can serve, allowing RPC
108+
// routers to determine which queries can be answered without hitting
109+
// "history pruned" errors.
110+
func (api *BlockChainAPI) Capabilities() *Capabilities {
111+
head := api.b.CurrentHeader()
112+
return buildCapabilities(
113+
head.Number.Uint64(),
114+
head.Hash(),
115+
api.b.HistoryPruningCutoff(),
116+
api.b.HistoryRetention(),
117+
)
118+
}
119+
120+
// buildCapabilities computes the eth_capabilities response from the head
121+
// block, the absolute history pruning cutoff, and the configured retention
122+
// windows. It is split out from the RPC method so the mapping rules can be
123+
// unit tested without a backend.
124+
func buildCapabilities(headNum uint64, headHash common.Hash, cutoff uint64, ret HistoryRetention) *Capabilities {
125+
// windowOldest returns the oldest block reachable through a sliding
126+
// window of `window` blocks, never going below the absolute history
127+
// pruning cutoff. A window of zero means "no sliding deletion" and
128+
// reports the cutoff itself.
129+
windowOldest := func(window uint64) uint64 {
130+
if window == 0 || headNum+1 <= window {
131+
return cutoff
132+
}
133+
oldest := headNum + 1 - window
134+
if oldest < cutoff {
135+
return cutoff
136+
}
137+
return oldest
138+
}
139+
140+
// resource builds a CapabilityResource for a window-style resource.
141+
// A window of zero is reported as deleteStrategy "none".
142+
resource := func(disabled bool, window uint64) CapabilityResource {
143+
ds := strategyNone()
144+
if window != 0 {
145+
ds = strategyWindow(window)
146+
}
147+
return CapabilityResource{
148+
Disabled: disabled,
149+
OldestBlock: hexutil.Uint64(windowOldest(window)),
150+
DeleteStrategy: ds,
151+
}
152+
}
153+
154+
// Headers, bodies and receipts share the same retention model in
155+
// geth: they are either kept in full ("all") or pruned to a fixed
156+
// boundary ("postmerge"). In neither case is there a sliding
157+
// deletion window, so the strategy is always "none" and the oldest
158+
// block equals the history pruning cutoff.
159+
blocks := CapabilityResource{
160+
Disabled: false,
161+
OldestBlock: hexutil.Uint64(cutoff),
162+
DeleteStrategy: strategyNone(),
163+
}
164+
receipts := blocks
165+
166+
tx := resource(false, ret.TxIndexHistory)
167+
logs := resource(ret.LogIndexDisabled, ret.LogIndexHistory)
168+
169+
// State availability depends on the storage scheme:
170+
//
171+
// - hash scheme: archive when StateHistory == 0; otherwise only
172+
// the head state is reachable, with no sliding
173+
// window.
174+
// - path scheme: honors the configured StateHistory window.
175+
var state CapabilityResource
176+
switch {
177+
case ret.StateScheme == rawdb.HashScheme && ret.StateHistory == 0:
178+
state = CapabilityResource{
179+
Disabled: false,
180+
OldestBlock: hexutil.Uint64(cutoff),
181+
DeleteStrategy: strategyNone(),
182+
}
183+
case ret.StateScheme == rawdb.HashScheme:
184+
state = CapabilityResource{
185+
Disabled: false,
186+
OldestBlock: hexutil.Uint64(headNum),
187+
DeleteStrategy: strategyNone(),
188+
}
189+
default:
190+
state = resource(false, ret.StateHistory)
191+
}
192+
193+
// eth_getProof availability tracks state availability one-for-one.
194+
stateproofs := state
195+
196+
return &Capabilities{
197+
Head: CapabilityHead{
198+
BlockNumber: hexutil.Uint64(headNum),
199+
BlockHash: headHash,
200+
},
201+
State: state,
202+
Tx: tx,
203+
Logs: logs,
204+
Receipts: receipts,
205+
Blocks: blocks,
206+
StateProofs: stateproofs,
207+
}
208+
}

0 commit comments

Comments
 (0)