mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
internal/ethapi: align capabilities response with spec
This commit is contained in:
parent
f8fb64a285
commit
e4ac40b5b5
2 changed files with 149 additions and 119 deletions
|
|
@ -76,41 +76,36 @@ type Capabilities struct {
|
|||
|
||||
// CapabilityHead is the current canonical head as reported by the node.
|
||||
type CapabilityHead struct {
|
||||
BlockNumber hexutil.Uint64 `json:"blockNumber"`
|
||||
BlockHash common.Hash `json:"blockHash"`
|
||||
Number hexutil.Uint64 `json:"number"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
}
|
||||
|
||||
// CapabilityResource describes the availability of a single data resource.
|
||||
type CapabilityResource struct {
|
||||
Disabled bool `json:"disabled"`
|
||||
OldestBlock hexutil.Uint64 `json:"oldestBlock"`
|
||||
DeleteStrategy DeleteStrategy `json:"deleteStrategy"`
|
||||
Disabled bool `json:"disabled"`
|
||||
OldestBlock *hexutil.Uint64 `json:"oldestBlock,omitempty"`
|
||||
DeleteStrategy *DeleteStrategy `json:"deleteStrategy,omitempty"`
|
||||
}
|
||||
|
||||
// DeleteStrategy describes how data of a resource is removed over time.
|
||||
//
|
||||
// Two strategies are defined by the spec:
|
||||
//
|
||||
// - "none": data is never deleted; the resource is permanently
|
||||
// retained from oldestBlock onwards.
|
||||
// - "window": data is retained for a sliding window of the most recent
|
||||
// RetentionBlocks blocks.
|
||||
//
|
||||
// RetentionBlocks is omitted from the JSON output for the "none" strategy.
|
||||
// The spec currently defines one strategy: "window", meaning data is retained
|
||||
// for a sliding window of the most recent RetentionBlocks blocks. Resources
|
||||
// without sliding deletion omit deleteStrategy.
|
||||
type DeleteStrategy struct {
|
||||
Type string `json:"type"`
|
||||
RetentionBlocks *uint64 `json:"retentionBlocks,omitempty"`
|
||||
}
|
||||
|
||||
// strategyNone returns a DeleteStrategy with type "none".
|
||||
func strategyNone() DeleteStrategy {
|
||||
return DeleteStrategy{Type: "none"}
|
||||
}
|
||||
|
||||
// strategyWindow returns a DeleteStrategy with type "window" and the given
|
||||
// retention block count.
|
||||
func strategyWindow(retention uint64) DeleteStrategy {
|
||||
return DeleteStrategy{Type: "window", RetentionBlocks: &retention}
|
||||
func strategyWindow(retention uint64) *DeleteStrategy {
|
||||
return &DeleteStrategy{Type: "window", RetentionBlocks: &retention}
|
||||
}
|
||||
|
||||
func capabilityOldestBlock(number uint64) *hexutil.Uint64 {
|
||||
oldest := hexutil.Uint64(number)
|
||||
return &oldest
|
||||
}
|
||||
|
||||
// Capabilities implements the eth_capabilities RPC method as defined in
|
||||
|
|
@ -149,28 +144,28 @@ func buildCapabilities(headNum uint64, headHash common.Hash, cutoff uint64, ret
|
|||
}
|
||||
|
||||
// resource builds a CapabilityResource for a window-style resource.
|
||||
// A window of zero is reported as deleteStrategy "none".
|
||||
// Disabled resources intentionally omit oldestBlock and deleteStrategy,
|
||||
// because those fields would otherwise look like usable history ranges.
|
||||
resource := func(disabled bool, window uint64, floor uint64) CapabilityResource {
|
||||
ds := strategyNone()
|
||||
if disabled {
|
||||
return CapabilityResource{Disabled: true}
|
||||
}
|
||||
res := CapabilityResource{
|
||||
OldestBlock: capabilityOldestBlock(windowOldest(window, floor)),
|
||||
}
|
||||
if window != 0 {
|
||||
ds = strategyWindow(window)
|
||||
}
|
||||
return CapabilityResource{
|
||||
Disabled: disabled,
|
||||
OldestBlock: hexutil.Uint64(windowOldest(window, floor)),
|
||||
DeleteStrategy: ds,
|
||||
res.DeleteStrategy = strategyWindow(window)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Bodies and receipts share the same retention model in
|
||||
// geth: they are either kept in full ("all") or pruned to a fixed
|
||||
// boundary ("postmerge"). In neither case is there a sliding
|
||||
// deletion window, so the strategy is always "none" and the oldest
|
||||
// block equals the history pruning cutoff.
|
||||
// boundary ("postmerge"). In neither case is there a sliding deletion
|
||||
// window, so deleteStrategy is omitted and oldestBlock equals the history
|
||||
// pruning cutoff.
|
||||
blocks := CapabilityResource{
|
||||
Disabled: false,
|
||||
OldestBlock: hexutil.Uint64(cutoff),
|
||||
DeleteStrategy: strategyNone(),
|
||||
OldestBlock: capabilityOldestBlock(cutoff),
|
||||
}
|
||||
receipts := blocks
|
||||
|
||||
|
|
@ -210,8 +205,8 @@ func buildCapabilities(headNum uint64, headHash common.Hash, cutoff uint64, ret
|
|||
|
||||
return &Capabilities{
|
||||
Head: CapabilityHead{
|
||||
BlockNumber: hexutil.Uint64(headNum),
|
||||
BlockHash: headHash,
|
||||
Number: hexutil.Uint64(headNum),
|
||||
Hash: headHash,
|
||||
},
|
||||
State: state,
|
||||
Tx: tx,
|
||||
|
|
|
|||
|
|
@ -33,10 +33,6 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
)
|
||||
headHash := common.HexToHash("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
|
||||
|
||||
// retentionWindow is a small helper for asserting on
|
||||
// CapabilityResource fields.
|
||||
retentionWindow := func(n uint64) *uint64 { return &n }
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
headNum uint64
|
||||
|
|
@ -53,12 +49,12 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
StateScheme: rawdb.PathScheme,
|
||||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"blocks": {OldestBlock: 0, DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"receipts": {OldestBlock: 0, DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"tx": {OldestBlock: 0, DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"logs": {OldestBlock: 0, DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"state": {OldestBlock: 0, DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"stateproofs": {OldestBlock: 0, DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"blocks": {OldestBlock: hexUintPtr(0)},
|
||||
"receipts": {OldestBlock: hexUintPtr(0)},
|
||||
"tx": {OldestBlock: hexUintPtr(0)},
|
||||
"logs": {OldestBlock: hexUintPtr(0)},
|
||||
"state": {OldestBlock: hexUintPtr(0)},
|
||||
"stateproofs": {OldestBlock: hexUintPtr(0)},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -71,8 +67,8 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
expected: map[string]CapabilityResource{
|
||||
// blocks/receipts honor the absolute cutoff with no
|
||||
// sliding window.
|
||||
"blocks": {OldestBlock: hexUint(postmerge), DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"receipts": {OldestBlock: hexUint(postmerge), DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"blocks": {OldestBlock: hexUintPtr(postmerge)},
|
||||
"receipts": {OldestBlock: hexUintPtr(postmerge)},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -86,12 +82,12 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"tx": {
|
||||
OldestBlock: hexUint(5_000_000 - 2_350_000 + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(2_350_000)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - 2_350_000 + 1),
|
||||
DeleteStrategy: windowStrategy(2_350_000),
|
||||
},
|
||||
"logs": {
|
||||
OldestBlock: hexUint(5_000_000 - 2_350_000 + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(2_350_000)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - 2_350_000 + 1),
|
||||
DeleteStrategy: windowStrategy(2_350_000),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -105,8 +101,8 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"tx": {
|
||||
OldestBlock: 0,
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(2_350_000)},
|
||||
OldestBlock: hexUintPtr(0),
|
||||
DeleteStrategy: windowStrategy(2_350_000),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -120,8 +116,8 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"tx": {
|
||||
OldestBlock: hexUint(4_000_000),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(2_350_000)},
|
||||
OldestBlock: hexUintPtr(4_000_000),
|
||||
DeleteStrategy: windowStrategy(2_350_000),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -137,12 +133,12 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"state": {
|
||||
OldestBlock: hexUint(5_000_000 - 90_000 + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(90_000)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - 90_000 + 1),
|
||||
DeleteStrategy: windowStrategy(90_000),
|
||||
},
|
||||
"stateproofs": {
|
||||
OldestBlock: hexUint(5_000_000 - 100_000 + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(100_000)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - 100_000 + 1),
|
||||
DeleteStrategy: windowStrategy(100_000),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -157,9 +153,7 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"logs": {
|
||||
Disabled: true,
|
||||
OldestBlock: hexUint(5_000_000 - 2_350_000 + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(2_350_000)},
|
||||
Disabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -175,12 +169,12 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"state": {
|
||||
OldestBlock: hexUint(5_000_000 - 90_000 + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(90_000)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - 90_000 + 1),
|
||||
DeleteStrategy: windowStrategy(90_000),
|
||||
},
|
||||
"stateproofs": {
|
||||
OldestBlock: hexUint(5_000_000 - 50_000 + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(50_000)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - 50_000 + 1),
|
||||
DeleteStrategy: windowStrategy(50_000),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -196,12 +190,12 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"state": {
|
||||
OldestBlock: hexUint(5_000_000 - 90_000 + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(90_000)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - 90_000 + 1),
|
||||
DeleteStrategy: windowStrategy(90_000),
|
||||
},
|
||||
"stateproofs": {
|
||||
OldestBlock: hexUint(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(corestate.TriesInMemory)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: windowStrategy(corestate.TriesInMemory),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -215,8 +209,8 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
StateHistory: 90_000,
|
||||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"state": {OldestBlock: 0, DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"stateproofs": {OldestBlock: 0, DeleteStrategy: DeleteStrategy{Type: "none"}},
|
||||
"state": {OldestBlock: hexUintPtr(0)},
|
||||
"stateproofs": {OldestBlock: hexUintPtr(0)},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -229,12 +223,12 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"state": {
|
||||
OldestBlock: hexUint(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(corestate.TriesInMemory)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: windowStrategy(corestate.TriesInMemory),
|
||||
},
|
||||
"stateproofs": {
|
||||
OldestBlock: hexUint(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(corestate.TriesInMemory)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: windowStrategy(corestate.TriesInMemory),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -248,12 +242,12 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
},
|
||||
expected: map[string]CapabilityResource{
|
||||
"state": {
|
||||
OldestBlock: hexUint(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(corestate.TriesInMemory)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: windowStrategy(corestate.TriesInMemory),
|
||||
},
|
||||
"stateproofs": {
|
||||
OldestBlock: hexUint(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: DeleteStrategy{Type: "window", RetentionBlocks: retentionWindow(corestate.TriesInMemory)},
|
||||
OldestBlock: hexUintPtr(5_000_000 - corestate.TriesInMemory + 1),
|
||||
DeleteStrategy: windowStrategy(corestate.TriesInMemory),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -264,11 +258,11 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
caps := buildCapabilities(tt.headNum, headHash, tt.cutoff, tt.ret)
|
||||
|
||||
// Head is always present.
|
||||
if uint64(caps.Head.BlockNumber) != tt.headNum {
|
||||
t.Errorf("head.blockNumber = %d, want %d", uint64(caps.Head.BlockNumber), tt.headNum)
|
||||
if uint64(caps.Head.Number) != tt.headNum {
|
||||
t.Errorf("head.number = %d, want %d", uint64(caps.Head.Number), tt.headNum)
|
||||
}
|
||||
if caps.Head.BlockHash != headHash {
|
||||
t.Errorf("head.blockHash = %x, want %x", caps.Head.BlockHash, headHash)
|
||||
if caps.Head.Hash != headHash {
|
||||
t.Errorf("head.hash = %x, want %x", caps.Head.Hash, headHash)
|
||||
}
|
||||
|
||||
actual := map[string]CapabilityResource{
|
||||
|
|
@ -284,23 +278,38 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
if got.Disabled != want.Disabled {
|
||||
t.Errorf("%s.disabled = %v, want %v", name, got.Disabled, want.Disabled)
|
||||
}
|
||||
if got.OldestBlock != want.OldestBlock {
|
||||
t.Errorf("%s.oldestBlock = %d, want %d", name, uint64(got.OldestBlock), uint64(want.OldestBlock))
|
||||
}
|
||||
if got.DeleteStrategy.Type != want.DeleteStrategy.Type {
|
||||
t.Errorf("%s.deleteStrategy.type = %q, want %q", name, got.DeleteStrategy.Type, want.DeleteStrategy.Type)
|
||||
switch {
|
||||
case want.OldestBlock == nil && got.OldestBlock != nil:
|
||||
t.Errorf("%s.oldestBlock = %d, want absent", name, uint64(*got.OldestBlock))
|
||||
case want.OldestBlock != nil && got.OldestBlock == nil:
|
||||
t.Errorf("%s.oldestBlock absent, want %d", name, uint64(*want.OldestBlock))
|
||||
case want.OldestBlock != nil && got.OldestBlock != nil:
|
||||
if *got.OldestBlock != *want.OldestBlock {
|
||||
t.Errorf("%s.oldestBlock = %d, want %d",
|
||||
name, uint64(*got.OldestBlock), uint64(*want.OldestBlock))
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case want.DeleteStrategy.RetentionBlocks == nil && got.DeleteStrategy.RetentionBlocks != nil:
|
||||
t.Errorf("%s.deleteStrategy.retentionBlocks = %d, want absent",
|
||||
name, *got.DeleteStrategy.RetentionBlocks)
|
||||
case want.DeleteStrategy.RetentionBlocks != nil && got.DeleteStrategy.RetentionBlocks == nil:
|
||||
t.Errorf("%s.deleteStrategy.retentionBlocks absent, want %d",
|
||||
name, *want.DeleteStrategy.RetentionBlocks)
|
||||
case want.DeleteStrategy.RetentionBlocks != nil && got.DeleteStrategy.RetentionBlocks != nil:
|
||||
if *got.DeleteStrategy.RetentionBlocks != *want.DeleteStrategy.RetentionBlocks {
|
||||
t.Errorf("%s.deleteStrategy.retentionBlocks = %d, want %d",
|
||||
name, *got.DeleteStrategy.RetentionBlocks, *want.DeleteStrategy.RetentionBlocks)
|
||||
case want.DeleteStrategy == nil && got.DeleteStrategy != nil:
|
||||
t.Errorf("%s.deleteStrategy = %#v, want absent", name, got.DeleteStrategy)
|
||||
case want.DeleteStrategy != nil && got.DeleteStrategy == nil:
|
||||
t.Errorf("%s.deleteStrategy absent, want %#v", name, want.DeleteStrategy)
|
||||
case want.DeleteStrategy != nil && got.DeleteStrategy != nil:
|
||||
if got.DeleteStrategy.Type != want.DeleteStrategy.Type {
|
||||
t.Errorf("%s.deleteStrategy.type = %q, want %q", name, got.DeleteStrategy.Type, want.DeleteStrategy.Type)
|
||||
}
|
||||
switch {
|
||||
case want.DeleteStrategy.RetentionBlocks == nil && got.DeleteStrategy.RetentionBlocks != nil:
|
||||
t.Errorf("%s.deleteStrategy.retentionBlocks = %d, want absent",
|
||||
name, *got.DeleteStrategy.RetentionBlocks)
|
||||
case want.DeleteStrategy.RetentionBlocks != nil && got.DeleteStrategy.RetentionBlocks == nil:
|
||||
t.Errorf("%s.deleteStrategy.retentionBlocks absent, want %d",
|
||||
name, *want.DeleteStrategy.RetentionBlocks)
|
||||
case want.DeleteStrategy.RetentionBlocks != nil && got.DeleteStrategy.RetentionBlocks != nil:
|
||||
if *got.DeleteStrategy.RetentionBlocks != *want.DeleteStrategy.RetentionBlocks {
|
||||
t.Errorf("%s.deleteStrategy.retentionBlocks = %d, want %d",
|
||||
name, *got.DeleteStrategy.RetentionBlocks, *want.DeleteStrategy.RetentionBlocks)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -310,18 +319,20 @@ func TestBuildCapabilities(t *testing.T) {
|
|||
|
||||
// TestCapabilitiesJSONShape verifies that the marshalled JSON conforms to
|
||||
// the schema defined in https://github.com/ethereum/execution-apis/pull/755:
|
||||
// "none" strategies must omit retentionBlocks, oldestBlock must be a hex
|
||||
// quantity, retentionBlocks must be a decimal integer.
|
||||
// head fields are named number/hash, resources without a sliding window omit
|
||||
// deleteStrategy, disabled resources omit range fields, and retentionBlocks is
|
||||
// a decimal integer.
|
||||
func TestCapabilitiesJSONShape(t *testing.T) {
|
||||
caps := buildCapabilities(
|
||||
5_000_000,
|
||||
common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"),
|
||||
0,
|
||||
HistoryRetention{
|
||||
StateScheme: rawdb.PathScheme,
|
||||
TxIndexHistory: 2_350_000,
|
||||
LogIndexHistory: 2_350_000,
|
||||
StateHistory: 90_000,
|
||||
StateScheme: rawdb.PathScheme,
|
||||
TxIndexHistory: 2_350_000,
|
||||
LogIndexHistory: 2_350_000,
|
||||
LogIndexDisabled: true,
|
||||
StateHistory: 90_000,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
@ -344,23 +355,40 @@ func TestCapabilitiesJSONShape(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// head.blockNumber must be a hex string ("0x..."), blockHash must be a 0x hash.
|
||||
// head.number must be a hex string ("0x..."), hash must be a 0x hash.
|
||||
head := generic["head"].(map[string]any)
|
||||
if bn, ok := head["blockNumber"].(string); !ok || len(bn) < 3 || bn[:2] != "0x" {
|
||||
t.Errorf("head.blockNumber not hex string: %v", head["blockNumber"])
|
||||
if number, ok := head["number"].(string); !ok || len(number) < 3 || number[:2] != "0x" {
|
||||
t.Errorf("head.number not hex string: %v", head["number"])
|
||||
}
|
||||
if bh, ok := head["blockHash"].(string); !ok || len(bh) != 66 {
|
||||
t.Errorf("head.blockHash not 32-byte hex string: %v", head["blockHash"])
|
||||
if hash, ok := head["hash"].(string); !ok || len(hash) != 66 {
|
||||
t.Errorf("head.hash not 32-byte hex string: %v", head["hash"])
|
||||
}
|
||||
if _, present := head["blockNumber"]; present {
|
||||
t.Errorf("head must not include blockNumber")
|
||||
}
|
||||
if _, present := head["blockHash"]; present {
|
||||
t.Errorf("head must not include blockHash")
|
||||
}
|
||||
|
||||
// blocks.deleteStrategy is "none" → must NOT contain retentionBlocks.
|
||||
// blocks have a fixed oldest block but no deletion strategy.
|
||||
blocks := generic["blocks"].(map[string]any)
|
||||
bds := blocks["deleteStrategy"].(map[string]any)
|
||||
if bds["type"] != "none" {
|
||||
t.Errorf("blocks.deleteStrategy.type = %v, want none", bds["type"])
|
||||
if ob, ok := blocks["oldestBlock"].(string); !ok || len(ob) < 3 || ob[:2] != "0x" {
|
||||
t.Errorf("blocks.oldestBlock not hex string: %v", blocks["oldestBlock"])
|
||||
}
|
||||
if _, present := bds["retentionBlocks"]; present {
|
||||
t.Errorf("blocks.deleteStrategy must not include retentionBlocks for type=none")
|
||||
if _, present := blocks["deleteStrategy"]; present {
|
||||
t.Errorf("blocks must not include deleteStrategy without sliding deletion")
|
||||
}
|
||||
|
||||
// Disabled resources must not advertise an availability range.
|
||||
logs := generic["logs"].(map[string]any)
|
||||
if logs["disabled"] != true {
|
||||
t.Errorf("logs.disabled = %v, want true", logs["disabled"])
|
||||
}
|
||||
if _, present := logs["oldestBlock"]; present {
|
||||
t.Errorf("disabled logs must not include oldestBlock")
|
||||
}
|
||||
if _, present := logs["deleteStrategy"]; present {
|
||||
t.Errorf("disabled logs must not include deleteStrategy")
|
||||
}
|
||||
|
||||
// tx.deleteStrategy is "window" → must contain retentionBlocks as a
|
||||
|
|
@ -385,5 +413,12 @@ func TestCapabilitiesJSONShape(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// hexUint is a small helper to keep the test tables compact.
|
||||
func hexUint(n uint64) hexutil.Uint64 { return hexutil.Uint64(n) }
|
||||
// hexUintPtr and windowStrategy keep the test tables compact.
|
||||
func hexUintPtr(n uint64) *hexutil.Uint64 {
|
||||
v := hexutil.Uint64(n)
|
||||
return &v
|
||||
}
|
||||
|
||||
func windowStrategy(n uint64) *DeleteStrategy {
|
||||
return &DeleteStrategy{Type: "window", RetentionBlocks: &n}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue