diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7620cf2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: Test + +on: + pull_request: + branches: '*' + push: + branches: + - main + +jobs: + test: + name: Run unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v3 + with: + go-version-file: go.mod + check-latest: true + - name: Run unit tests + run: | + go test -v -p 1 ./... + go test -v ./tracker -count 20 diff --git a/go.mod b/go.mod index f5127e9..263e145 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/cerc-io/eth-iterator-utils -go 1.19 +go 1.21 require ( github.com/cerc-io/eth-testing v0.4.0 diff --git a/tracker/tracker.go b/tracker/tracker.go index 730eb58..48b8a3a 100644 --- a/tracker/tracker.go +++ b/tracker/tracker.go @@ -189,7 +189,7 @@ func (tr *TrackerImpl) Restore(makeIterator iter.IteratorConstructor) ( } } - // force the lower bound path to an even length (required by geth API/HexToKeyBytes) + // force the lower bound path to an even length (required by NodeIterator constructor) if len(recoveredPath)&1 == 1 { // to avoid skipped nodes, we must rewind by one index recoveredPath = rewindPath(recoveredPath) @@ -246,6 +246,7 @@ func (it *Iterator) Next(descend bool) bool { return ret } +// Bounds returns the bounds of the underlying PrefixBoundIterator, if any func (it *Iterator) Bounds() ([]byte, []byte) { if impl, ok := it.NodeIterator.(*iter.PrefixBoundIterator); ok { return impl.Bounds() @@ -253,20 +254,21 @@ func (it *Iterator) Bounds() ([]byte, []byte) { return nil, nil } -// Rewinds to the path of the previous (pre-order) node: +// Returns the path, rewound to the previous (pre-order) node: +// If path is the root (empty) or a leaf path, it's returned. // If the last byte of the path is zero, pops it (e.g. [1 0] => [1]). // Otherwise, decrements it and pads with 0xF to 64 bytes (e.g. [1] => [0 f f f ...]). // The passed slice is not modified. func rewindPath(path []byte) []byte { - if len(path) == 0 { + if len(path) == 0 || path[len(path)-1] == 0x10 { return path } if path[len(path)-1] == 0 { return path[:len(path)-1] } - path[len(path)-1]-- padded := make([]byte, 64) i := copy(padded, path) + padded[len(path)-1]-- for ; i < len(padded); i++ { padded[i] = 0xf } diff --git a/tracker/tracker_test.go b/tracker/tracker_test.go index ae8f61d..bfcc9c5 100644 --- a/tracker/tracker_test.go +++ b/tracker/tracker_test.go @@ -25,7 +25,6 @@ func TestTracker(t *testing.T) { tr := tracker.New(recoveryFile, NumIters) defer tr.CloseAndSave() - var prevPath []byte count := 0 nodeit, err := tree.NodeIterator(nil) if err != nil { @@ -33,9 +32,9 @@ func TestTracker(t *testing.T) { } for it := tr.Tracked(nodeit); it.Next(true); { if count == interrupt { - return prevPath // tracker rewinds one node to prevent gaps + t.Logf("interrupting at: i=%d path=%v", count, it.Path()) + return it.Path() } - prevPath = it.Path() count++ } return nil @@ -58,8 +57,18 @@ func TestTracker(t *testing.T) { if uint(len(its)) != NumIters { t.Fatalf("expected to restore %d iterators, got %d", NumIters, len(its)) } + if !its[0].Next(true) { + t.Fatal("iterator ends prematurely after restore") + } if !bytes.Equal(failedAt, its[0].Path()) { - t.Fatalf("iterator restored to wrong position: expected %v, got %v", failedAt, its[0].Path()) + // Due to the constraint that NodeIterator can only be initialized with an even-length path, + // we sometimes rewind an extra node when restoring (e.g. [1 2 0] => [1 2]). + if !its[0].Next(true) { + t.Fatal("iterator ends prematurely after restore") + } + if !bytes.Equal(failedAt, its[0].Path()) { + t.Fatalf("iterator restored to wrong position: expected %v, got %v", failedAt, its[0].Path()) + } } if fileExists(recoveryFile) {