go-ethereum/rlp/iterator_test.go
Felix Lange ac85a6f254
rlp: add back Iterator.Count, with fixes (#33841)
I removed `Iterator.Count` in #33840, because it appeared to be unused
and did not provide the documented invariant: the returned count should
always be an upper bound on the number of iterations allowed by `Next`.

In order to make `Count` work, the semantics of `CountValues` has to
change to return the number of items up and including the invalid one. I
have reviewed all callsites of `CountValues` to assess if changing this
is safe. There aren't that many, and the only call that doesn't check
the error and return is in the trie node parser,
`trie.decodeNodeUnsafe`. There, we distinguish the node type based on
the number of items, and it previously returned an error for item count
zero. In order to avoid any potential issue that could result from this
change, I'm adding an error check in that function, though it isn't
necessary.
2026-02-13 23:53:42 +01:00

133 lines
5 KiB
Go

// Copyright 2020 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package rlp
import (
"io"
"testing"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// TestIterator tests some basic things about the ListIterator. A more
// comprehensive test can be found in core/rlp_test.go, where we can
// use both types and rlp without dependency cycles
func TestIterator(t *testing.T) {
bodyRlpHex := "0xf902cbf8d6f869800182c35094000000000000000000000000000000000000aaaa808a000000000000000000001ba01025c66fad28b4ce3370222624d952c35529e602af7cbe04f667371f61b0e3b3a00ab8813514d1217059748fd903288ace1b4001a4bc5fbde2790debdc8167de2ff869010182c35094000000000000000000000000000000000000aaaa808a000000000000000000001ca05ac4cf1d19be06f3742c21df6c49a7e929ceb3dbaf6a09f3cfb56ff6828bd9a7a06875970133a35e63ac06d360aa166d228cc013e9b96e0a2cae7f55b22e1ee2e8f901f0f901eda0c75448377c0e426b8017b23c5f77379ecf69abc1d5c224284ad3ba1c46c59adaa00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808080808080a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"
bodyRlp := hexutil.MustDecode(bodyRlpHex)
it, err := NewListIterator(bodyRlp)
if err != nil {
t.Fatal(err)
}
// Check that txs exist
if !it.Next() {
t.Fatal("expected two elems, got zero")
}
txs := it.Value()
if offset := it.Offset(); offset != 3 {
t.Fatal("wrong offset", offset, "want 3")
}
// Check that uncles exist
if !it.Next() {
t.Fatal("expected two elems, got one")
}
if offset := it.Offset(); offset != 219 {
t.Fatal("wrong offset", offset, "want 219")
}
txit, err := NewListIterator(txs)
if err != nil {
t.Fatal(err)
}
if c := txit.Count(); c != 2 {
t.Fatal("wrong Count:", c)
}
var i = 0
for txit.Next() {
if txit.err != nil {
t.Fatal(txit.err)
}
i++
}
if exp := 2; i != exp {
t.Errorf("count wrong, expected %d got %d", i, exp)
}
}
func TestIteratorErrors(t *testing.T) {
tests := []struct {
input []byte
wantCount int // expected Count before iterating
wantErr error
}{
// Second item string header claims 3 bytes content, but only 2 remain.
{unhex("C4 01 83AABB"), 2, ErrValueTooLarge},
// Second item truncated: B9 requires 2 size bytes, none available.
{unhex("C2 01 B9"), 2, io.ErrUnexpectedEOF},
// 0x05 should be encoded directly, not as 81 05.
{unhex("C3 01 8105"), 2, ErrCanonSize},
// Long-form string header B8 used for 1-byte content (< 56).
{unhex("C4 01 B801AA"), 2, ErrCanonSize},
// Long-form list header F8 used for 1-byte content (< 56).
{unhex("C4 01 F80101"), 2, ErrCanonSize},
}
for _, tt := range tests {
it, err := NewListIterator(tt.input)
if err != nil {
t.Fatal("NewListIterator error:", err)
}
if c := it.Count(); c != tt.wantCount {
t.Fatalf("%x: Count = %d, want %d", tt.input, c, tt.wantCount)
}
n := 0
for it.Next() {
if it.Err() != nil {
break
}
n++
}
if wantN := tt.wantCount - 1; n != wantN {
t.Fatalf("%x: got %d valid items, want %d", tt.input, n, wantN)
}
if it.Err() != tt.wantErr {
t.Fatalf("%x: got error %v, want %v", tt.input, it.Err(), tt.wantErr)
}
if it.Next() {
t.Fatalf("%x: Next returned true after error", tt.input)
}
}
}
func FuzzIteratorCount(f *testing.F) {
examples := [][]byte{unhex("010203"), unhex("018142"), unhex("01830202")}
for _, e := range examples {
f.Add(e)
}
f.Fuzz(func(t *testing.T, in []byte) {
it := newIterator(in, 0)
count := it.Count()
i := 0
for it.Next() {
i++
}
if i != count {
t.Fatalf("%x: count %d not equal to %d iterations", in, count, i)
}
})
}