From 67e689e161357458696648c7d7eea1bb0111c206 Mon Sep 17 00:00:00 2001 From: Forostovec Date: Wed, 5 Nov 2025 10:33:34 +0200 Subject: [PATCH 1/5] triedb/pathdb: fix off-by-one for last state history ID and handle empty ranges --- triedb/pathdb/history_inspect.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/triedb/pathdb/history_inspect.go b/triedb/pathdb/history_inspect.go index 9b4eea27b454..e630d0467ee6 100644 --- a/triedb/pathdb/history_inspect.go +++ b/triedb/pathdb/history_inspect.go @@ -50,12 +50,12 @@ func sanitizeRange(start, end uint64, freezer ethdb.AncientReader) (uint64, uint if err != nil { return 0, 0, err } - last := head - 1 + last := head if end != 0 && end < last { last = end } // Make sure the range is valid - if first >= last { + if first > last { return 0, 0, fmt.Errorf("range is invalid, first: %d, last: %d", first, last) } return first, last, nil @@ -143,7 +143,11 @@ func historyRange(freezer ethdb.AncientReader) (uint64, uint64, error) { if err != nil { return 0, 0, err } - last := head - 1 + // If there is no history available, return an explicit error. + if head == tail { + return 0, 0, fmt.Errorf("no history available") + } + last := head fh, err := readStateHistory(freezer, first) if err != nil { From 591e8586911d0ecdfaac6296c07cf4a6e9381103 Mon Sep 17 00:00:00 2001 From: Forostovec Date: Thu, 6 Nov 2025 15:49:45 +0200 Subject: [PATCH 2/5] add test --- triedb/pathdb/history_inspect_test.go | 102 ++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 triedb/pathdb/history_inspect_test.go diff --git a/triedb/pathdb/history_inspect_test.go b/triedb/pathdb/history_inspect_test.go new file mode 100644 index 000000000000..663fc6461489 --- /dev/null +++ b/triedb/pathdb/history_inspect_test.go @@ -0,0 +1,102 @@ +package pathdb + +import ( + "errors" + "testing" + + "github.com/ethereum/go-ethereum/ethdb" +) + +// fakeAncientReader is a minimal AncientReader used for testing sanitizeRange. +// It only provides Tail and Ancients meaningful values; other methods are +// unimplemented and should not be called by these tests. +type fakeAncientReader struct { + tail uint64 + head uint64 +} + +// Implement ethdb.AncientReaderOp +func (f *fakeAncientReader) Ancient(kind string, number uint64) ([]byte, error) { + return nil, errors.New("not implemented") +} +func (f *fakeAncientReader) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { + return nil, errors.New("not implemented") +} +func (f *fakeAncientReader) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { + return nil, errors.New("not implemented") +} +func (f *fakeAncientReader) Ancients() (uint64, error) { return f.head, nil } +func (f *fakeAncientReader) Tail() (uint64, error) { return f.tail, nil } +func (f *fakeAncientReader) AncientSize(kind string) (uint64, error) { return 0, nil } + +// Implement ethdb.AncientReader +func (f *fakeAncientReader) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { + return fn(f) +} + +func TestSanitizeRange_SingleItem_AutoBounds(t *testing.T) { + // tail=4, head=5 => only one history with id=5 + fr := &fakeAncientReader{tail: 4, head: 5} + first, last, err := sanitizeRange(0, 0, fr) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if first != 5 || last != 5 { + t.Fatalf("want first=5,last=5; got first=%d,last=%d", first, last) + } +} + +func TestSanitizeRange_ExplicitSingleItem(t *testing.T) { + fr := &fakeAncientReader{tail: 10, head: 20} + first, last, err := sanitizeRange(15, 15, fr) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if first != 15 || last != 15 { + t.Fatalf("want first=15,last=15; got first=%d,last=%d", first, last) + } +} + +func TestSanitizeRange_EmptyStore_Error(t *testing.T) { + // head==tail indicates no histories available + fr := &fakeAncientReader{tail: 10, head: 10} + _, _, err := sanitizeRange(0, 0, fr) + if err == nil { + t.Fatalf("expected error for empty store, got nil") + } +} + +func TestSanitizeRange_AutoClampBounds(t *testing.T) { + fr := &fakeAncientReader{tail: 5, head: 10} + // start below first, end above last -> should clamp to [6,10] + first, last, err := sanitizeRange(3, 12, fr) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if first != 6 || last != 10 { + t.Fatalf("want first=6,last=10; got first=%d,last=%d", first, last) + } +} + +func TestSanitizeRange_StartGreaterThanEndAfterClamp_Error(t *testing.T) { + fr := &fakeAncientReader{tail: 5, head: 7} + // start beyond last while end inside -> becomes first=9,last=7 -> error + _, _, err := sanitizeRange(9, 6, fr) + if err == nil { + t.Fatalf("expected error when first > last after clamping, got nil") + } +} + +func TestSanitizeRange_LastEqualsHead(t *testing.T) { + fr := &fakeAncientReader{tail: 50, head: 100} + first, last, err := sanitizeRange(0, 0, fr) + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if last != 100 { + t.Fatalf("want last=head=100; got last=%d", last) + } + if first != 51 { + t.Fatalf("want first=tail+1=51; got first=%d", first) + } +} From 00e0f2b3b1af2a73aa9f0d682271074bca88c0c0 Mon Sep 17 00:00:00 2001 From: Forostovec Date: Mon, 10 Nov 2025 15:32:47 +0200 Subject: [PATCH 3/5] Delete triedb/pathdb/history_inspect_test.go --- triedb/pathdb/history_inspect_test.go | 102 -------------------------- 1 file changed, 102 deletions(-) delete mode 100644 triedb/pathdb/history_inspect_test.go diff --git a/triedb/pathdb/history_inspect_test.go b/triedb/pathdb/history_inspect_test.go deleted file mode 100644 index 663fc6461489..000000000000 --- a/triedb/pathdb/history_inspect_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package pathdb - -import ( - "errors" - "testing" - - "github.com/ethereum/go-ethereum/ethdb" -) - -// fakeAncientReader is a minimal AncientReader used for testing sanitizeRange. -// It only provides Tail and Ancients meaningful values; other methods are -// unimplemented and should not be called by these tests. -type fakeAncientReader struct { - tail uint64 - head uint64 -} - -// Implement ethdb.AncientReaderOp -func (f *fakeAncientReader) Ancient(kind string, number uint64) ([]byte, error) { - return nil, errors.New("not implemented") -} -func (f *fakeAncientReader) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { - return nil, errors.New("not implemented") -} -func (f *fakeAncientReader) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) { - return nil, errors.New("not implemented") -} -func (f *fakeAncientReader) Ancients() (uint64, error) { return f.head, nil } -func (f *fakeAncientReader) Tail() (uint64, error) { return f.tail, nil } -func (f *fakeAncientReader) AncientSize(kind string) (uint64, error) { return 0, nil } - -// Implement ethdb.AncientReader -func (f *fakeAncientReader) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { - return fn(f) -} - -func TestSanitizeRange_SingleItem_AutoBounds(t *testing.T) { - // tail=4, head=5 => only one history with id=5 - fr := &fakeAncientReader{tail: 4, head: 5} - first, last, err := sanitizeRange(0, 0, fr) - if err != nil { - t.Fatalf("unexpected err: %v", err) - } - if first != 5 || last != 5 { - t.Fatalf("want first=5,last=5; got first=%d,last=%d", first, last) - } -} - -func TestSanitizeRange_ExplicitSingleItem(t *testing.T) { - fr := &fakeAncientReader{tail: 10, head: 20} - first, last, err := sanitizeRange(15, 15, fr) - if err != nil { - t.Fatalf("unexpected err: %v", err) - } - if first != 15 || last != 15 { - t.Fatalf("want first=15,last=15; got first=%d,last=%d", first, last) - } -} - -func TestSanitizeRange_EmptyStore_Error(t *testing.T) { - // head==tail indicates no histories available - fr := &fakeAncientReader{tail: 10, head: 10} - _, _, err := sanitizeRange(0, 0, fr) - if err == nil { - t.Fatalf("expected error for empty store, got nil") - } -} - -func TestSanitizeRange_AutoClampBounds(t *testing.T) { - fr := &fakeAncientReader{tail: 5, head: 10} - // start below first, end above last -> should clamp to [6,10] - first, last, err := sanitizeRange(3, 12, fr) - if err != nil { - t.Fatalf("unexpected err: %v", err) - } - if first != 6 || last != 10 { - t.Fatalf("want first=6,last=10; got first=%d,last=%d", first, last) - } -} - -func TestSanitizeRange_StartGreaterThanEndAfterClamp_Error(t *testing.T) { - fr := &fakeAncientReader{tail: 5, head: 7} - // start beyond last while end inside -> becomes first=9,last=7 -> error - _, _, err := sanitizeRange(9, 6, fr) - if err == nil { - t.Fatalf("expected error when first > last after clamping, got nil") - } -} - -func TestSanitizeRange_LastEqualsHead(t *testing.T) { - fr := &fakeAncientReader{tail: 50, head: 100} - first, last, err := sanitizeRange(0, 0, fr) - if err != nil { - t.Fatalf("unexpected err: %v", err) - } - if last != 100 { - t.Fatalf("want last=head=100; got last=%d", last) - } - if first != 51 { - t.Fatalf("want first=tail+1=51; got first=%d", first) - } -} From 9cb3931d7529d05e94e2b4a4d73d5d8a4b9ff2d2 Mon Sep 17 00:00:00 2001 From: Forostovec Date: Mon, 10 Nov 2025 15:33:16 +0200 Subject: [PATCH 4/5] Update history_inspect.go --- triedb/pathdb/history_inspect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/triedb/pathdb/history_inspect.go b/triedb/pathdb/history_inspect.go index e630d0467ee6..2eb6374bebd4 100644 --- a/triedb/pathdb/history_inspect.go +++ b/triedb/pathdb/history_inspect.go @@ -55,7 +55,7 @@ func sanitizeRange(start, end uint64, freezer ethdb.AncientReader) (uint64, uint last = end } // Make sure the range is valid - if first > last { + if first >= last { return 0, 0, fmt.Errorf("range is invalid, first: %d, last: %d", first, last) } return first, last, nil From 49e72a828d368bc36aff11b8787d349d73293052 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Thu, 13 Nov 2025 14:49:46 +0800 Subject: [PATCH 5/5] Simplify error handling in history inspection Remove explicit error for no history available. --- triedb/pathdb/history_inspect.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/triedb/pathdb/history_inspect.go b/triedb/pathdb/history_inspect.go index 2eb6374bebd4..a839a184caa4 100644 --- a/triedb/pathdb/history_inspect.go +++ b/triedb/pathdb/history_inspect.go @@ -143,10 +143,6 @@ func historyRange(freezer ethdb.AncientReader) (uint64, uint64, error) { if err != nil { return 0, 0, err } - // If there is no history available, return an explicit error. - if head == tail { - return 0, 0, fmt.Errorf("no history available") - } last := head fh, err := readStateHistory(freezer, first)