diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c026cb3f4..fc46f9618 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: strategy: matrix: os: [ ubuntu-20.04, macos-11, windows-2022 ] # list of os: https://github.com/actions/virtual-environments + go: ['1.20', '1.21'] runs-on: ${{ matrix.os }} steps: @@ -33,7 +34,7 @@ jobs: fetch-depth: 0 # fetch git tags for "git describe" - uses: actions/setup-go@v4 with: - go-version: '1.19' + go-version: ${{ matrix.go }} - name: Install deps if: matrix.os == 'ubuntu-20.04' diff --git a/.golangci.yml b/.golangci.yml index a227f0e75..a339468d4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -40,6 +40,7 @@ linters-settings: # Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty disabled-checks: + - dupSubExpr - regexpMust # - hugeParam - rangeValCopy diff --git a/Makefile b/Makefile index fc2a547b4..86a039e0c 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ lintci-deps-clean: golangci-lint-clean # download and build golangci-lint (https://golangci-lint.run) $(GOBINREL)/golangci-lint: | $(GOBINREL) - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(GOBIN)" v1.54.0 + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(GOBIN)" v1.54.1 golangci-lint-clean: rm -f "$(GOBIN)/golangci-lint" diff --git a/common/address.go b/common/address.go index 4ad220345..c51db9d26 100644 --- a/common/address.go +++ b/common/address.go @@ -36,6 +36,9 @@ var ( // Address represents the 20 byte address of an Ethereum account. type Address [length.Addr]byte +// Cmp compares two addresses. +func (a Address) Cmp(other Address) int { return bytes.Compare(a[:], other[:]) } + // BytesToAddress returns Address with value b. // If b is larger than len(h), b will be cropped from the left. func BytesToAddress(b []byte) Address { diff --git a/common/cmp/cmp_1_20.go b/common/cmp/cmp_1_20.go new file mode 100644 index 000000000..9488f71ba --- /dev/null +++ b/common/cmp/cmp_1_20.go @@ -0,0 +1,73 @@ +/* + Copyright 2021 Erigon contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +//nolint + +package cmp + +// Copy-Paste of golang's 1.21.0 `cmp` package + +// Ordered is a constraint that permits any ordered type: any type +// that supports the operators < <= >= >. +// If future releases of Go add new ordered types, +// this constraint will be modified to include them. +// +// Note that floating-point types may contain NaN ("not-a-number") values. +// An operator such as == or < will always report false when +// comparing a NaN value with any other value, NaN or not. +// See the [Compare] function for a consistent way to compare NaN values. +type Ordered interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | + ~float32 | ~float64 | + ~string +} + +// Less reports whether x is less than y. +// For floating-point types, a NaN is considered less than any non-NaN, +// and -0.0 is not less than (is equal to) 0.0. +func Less[T Ordered](x, y T) bool { + return (isNaN(x) && !isNaN(y)) || x < y +} + +// Compare returns +// +// -1 if x is less than y, +// 0 if x equals y, +// +1 if x is greater than y. +// +// For floating-point types, a NaN is considered less than any non-NaN, +// a NaN is considered equal to a NaN, and -0.0 is equal to 0.0. +func Compare[T Ordered](x, y T) int { + xNaN := isNaN(x) + yNaN := isNaN(y) + if xNaN && yNaN { + return 0 + } + if xNaN || x < y { + return -1 + } + if yNaN || x > y { + return +1 + } + return 0 +} + +// isNaN reports whether x is a NaN without requiring the math package. +// This will always return false if T is not floating-point. +func isNaN[T Ordered](x T) bool { + return x != x //nolint +} diff --git a/common/hash.go b/common/hash.go index 72c4b570f..9d7a5e02e 100644 --- a/common/hash.go +++ b/common/hash.go @@ -39,6 +39,9 @@ const ( // Hash represents the 32 byte Keccak256 hash of arbitrary data. type Hash [length.Hash]byte +// Cmp compares two hashes. +func (h Hash) Cmp(other Hash) int { return bytes.Compare(h[:], other[:]) } + // BytesToHash sets b to hash. // If b is larger than len(h), b will be cropped from the left. func BytesToHash(b []byte) Hash { diff --git a/compress/compress.go b/compress/compress.go index 5b3d47c32..d5ddda9d0 100644 --- a/compress/compress.go +++ b/compress/compress.go @@ -33,6 +33,7 @@ import ( "github.com/c2h5oh/datasize" "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common/cmp" dir2 "github.com/ledgerwatch/erigon-lib/common/dir" "github.com/ledgerwatch/erigon-lib/etl" "github.com/ledgerwatch/log/v3" @@ -300,17 +301,17 @@ func (db *DictionaryBuilder) Less(i, j int) bool { return db.items[i].score < db.items[j].score } -func dictionaryBuilderLess(i, j *Pattern) bool { +func dictionaryBuilderCmp(i, j *Pattern) int { if i.score == j.score { - return bytes.Compare(i.word, j.word) < 0 + return bytes.Compare(i.word, j.word) } - return i.score < j.score + return cmp.Compare(i.score, j.score) } func (db *DictionaryBuilder) Swap(i, j int) { db.items[i], db.items[j] = db.items[j], db.items[i] } -func (db *DictionaryBuilder) Sort() { slices.SortFunc(db.items, dictionaryBuilderLess) } +func (db *DictionaryBuilder) Sort() { slices.SortFunc(db.items, dictionaryBuilderCmp) } func (db *DictionaryBuilder) Push(x interface{}) { db.items = append(db.items, x.(*Pattern)) @@ -383,11 +384,11 @@ type Pattern struct { type PatternList []*Pattern func (pl PatternList) Len() int { return len(pl) } -func patternListLess(i, j *Pattern) bool { +func patternListCmp(i, j *Pattern) int { if i.uses == j.uses { - return bits.Reverse64(i.code) < bits.Reverse64(j.code) + return cmp.Compare(bits.Reverse64(i.code), bits.Reverse64(j.code)) } - return i.uses < j.uses + return cmp.Compare(i.uses, j.uses) } // PatternHuff is an intermediate node in a huffman tree of patterns @@ -555,11 +556,11 @@ type PositionList []*Position func (pl PositionList) Len() int { return len(pl) } -func positionListLess(i, j *Position) bool { +func positionListCmp(i, j *Position) int { if i.uses == j.uses { - return bits.Reverse64(i.code) < bits.Reverse64(j.code) + return cmp.Compare(bits.Reverse64(i.code), bits.Reverse64(j.code)) } - return i.uses < j.uses + return cmp.Compare(i.uses, j.uses) } type PositionHeap []*PositionHuff diff --git a/compress/parallel_compress.go b/compress/parallel_compress.go index 1dd0d9508..04c853e9c 100644 --- a/compress/parallel_compress.go +++ b/compress/parallel_compress.go @@ -469,7 +469,7 @@ func reducedict(ctx context.Context, trace bool, logPrefix, segmentFilePath stri distribution[len(p.word)]++ } } - slices.SortFunc(patternList, patternListLess) + slices.SortFunc(patternList, patternListCmp) logCtx := make([]interface{}, 0, 8) logCtx = append(logCtx, "patternList.Len", patternList.Len()) @@ -551,7 +551,7 @@ func reducedict(ctx context.Context, trace bool, logPrefix, segmentFilePath stri } //fmt.Printf("patternsSize = %d\n", patternsSize) // Write all the pattens - slices.SortFunc(patternList, patternListLess) + slices.SortFunc(patternList, patternListCmp) for _, p := range patternList { ns := binary.PutUvarint(numBuf[:], uint64(p.depth)) if _, err = cw.Write(numBuf[:ns]); err != nil { @@ -574,7 +574,7 @@ func reducedict(ctx context.Context, trace bool, logPrefix, segmentFilePath stri positionList = append(positionList, p) pos2code[pos] = p } - slices.SortFunc(positionList, positionListLess) + slices.SortFunc(positionList, positionListCmp) i = 0 // Build Huffman tree for codes var posHeap PositionHeap @@ -632,7 +632,7 @@ func reducedict(ctx context.Context, trace bool, logPrefix, segmentFilePath stri } //fmt.Printf("posSize = %d\n", posSize) // Write all the positions - slices.SortFunc(positionList, positionListLess) + slices.SortFunc(positionList, positionListCmp) for _, p := range positionList { ns := binary.PutUvarint(numBuf[:], uint64(p.depth)) if _, err = cw.Write(numBuf[:ns]); err != nil { diff --git a/downloader/snaptype/files.go b/downloader/snaptype/files.go index 424596e07..419c06b8a 100644 --- a/downloader/snaptype/files.go +++ b/downloader/snaptype/files.go @@ -24,6 +24,7 @@ import ( "strconv" "strings" + cmp2 "github.com/ledgerwatch/erigon-lib/common/cmp" "github.com/ledgerwatch/erigon-lib/common/dir" "golang.org/x/exp/slices" ) @@ -226,20 +227,24 @@ func ParseDir(dir string) (res []FileInfo, err error) { } res = append(res, meta) } - slices.SortFunc(res, func(i, j FileInfo) bool { - if i.Version != j.Version { - return i.Version < j.Version + slices.SortFunc(res, func(i, j FileInfo) int { + cmp := cmp2.Compare(i.Version, j.Version) + if cmp != 0 { + return cmp } - if i.From != j.From { - return i.From < j.From + cmp = cmp2.Compare(i.From, j.From) + if cmp != 0 { + return cmp } - if i.To != j.To { - return i.To < j.To + cmp = cmp2.Compare(i.To, j.To) + if cmp != 0 { + return cmp } - if i.T != j.T { - return i.T < j.T + cmp = cmp2.Compare(i.T, j.T) + if cmp != 0 { + return cmp } - return i.Ext < j.Ext + return cmp2.Compare(i.Ext, j.Ext) }) return res, nil diff --git a/go.mod b/go.mod index d9508205a..7aab1c3b4 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/tidwall/btree v1.6.0 golang.org/x/crypto v0.12.0 - golang.org/x/exp v0.0.0-20230711023510-fffb14384f22 + golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb golang.org/x/sync v0.3.0 golang.org/x/sys v0.11.0 golang.org/x/time v0.3.0 diff --git a/go.sum b/go.sum index c96dbed31..163f717db 100644 --- a/go.sum +++ b/go.sum @@ -418,8 +418,8 @@ golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230711023510-fffb14384f22 h1:FqrVOBQxQ8r/UwwXibI0KMolVhvFiGobSfdE33deHJM= -golang.org/x/exp v0.0.0-20230711023510-fffb14384f22/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA= +golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/gointerfaces/remote/sort.go b/gointerfaces/remote/sort.go index 8145cfc3c..f61407bf0 100644 --- a/gointerfaces/remote/sort.go +++ b/gointerfaces/remote/sort.go @@ -6,9 +6,9 @@ import ( "github.com/ledgerwatch/erigon-lib/gointerfaces/types" ) -func NodeInfoReplyLess(i, j *types.NodeInfoReply) bool { +func NodeInfoReplyCmp(i, j *types.NodeInfoReply) int { if cmp := strings.Compare(i.Name, j.Name); cmp != 0 { - return cmp == -1 + return cmp } - return strings.Compare(i.Enode, j.Enode) == -1 + return strings.Compare(i.Enode, j.Enode) } diff --git a/gointerfaces/remote/sort_test.go b/gointerfaces/remote/sort_test.go index 46de9befa..50a7bfa26 100644 --- a/gointerfaces/remote/sort_test.go +++ b/gointerfaces/remote/sort_test.go @@ -3,10 +3,11 @@ package remote_test import ( "testing" - "github.com/ledgerwatch/erigon-lib/gointerfaces/remote" - "github.com/ledgerwatch/erigon-lib/gointerfaces/types" "github.com/stretchr/testify/assert" "golang.org/x/exp/slices" + + "github.com/ledgerwatch/erigon-lib/gointerfaces/remote" + "github.com/ledgerwatch/erigon-lib/gointerfaces/types" ) func TestSort(t *testing.T) { @@ -49,7 +50,7 @@ func TestSort(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - slices.SortFunc(tt.got.NodesInfo, remote.NodeInfoReplyLess) + slices.SortFunc(tt.got.NodesInfo, remote.NodeInfoReplyCmp) assert.Equal(t, tt.want, tt.got) }) } diff --git a/kv/iter/iter.go b/kv/iter/iter.go index d722caed1..a2d45a864 100644 --- a/kv/iter/iter.go +++ b/kv/iter/iter.go @@ -322,6 +322,7 @@ func (m *IntersectIter[T]) advanceX() { } } func (m *IntersectIter[T]) advanceY() { + if m.err != nil { return } diff --git a/patricia/patricia.go b/patricia/patricia.go index 76e1eea72..f2ccc86c5 100644 --- a/patricia/patricia.go +++ b/patricia/patricia.go @@ -21,6 +21,7 @@ import ( "math/bits" "strings" + "github.com/ledgerwatch/erigon-lib/common/cmp" "github.com/ledgerwatch/erigon-lib/sais" "golang.org/x/exp/slices" ) @@ -699,7 +700,7 @@ func (mf2 *MatchFinder2) FindLongestMatches(data []byte) []Match { return mf2.matches } //sort.Sort(&mf2.matches) - slices.SortFunc(mf2.matches, func(i, j Match) bool { return i.Start < j.Start }) + slices.SortFunc(mf2.matches, func(i, j Match) int { return cmp.Compare(i.Start, j.Start) }) lastEnd := mf2.matches[0].End j := 1 diff --git a/state/btree_index.go b/state/btree_index.go index 00d2b9e13..f82660cc5 100644 --- a/state/btree_index.go +++ b/state/btree_index.go @@ -16,12 +16,11 @@ import ( "github.com/c2h5oh/datasize" "github.com/edsrzf/mmap-go" - "github.com/ledgerwatch/erigon-lib/common/dbg" "github.com/ledgerwatch/log/v3" - "github.com/ledgerwatch/erigon-lib/common/background" - "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common/background" + "github.com/ledgerwatch/erigon-lib/common/dbg" "github.com/ledgerwatch/erigon-lib/common/length" "github.com/ledgerwatch/erigon-lib/compress" "github.com/ledgerwatch/erigon-lib/etl"