diff --git a/cl/cltypes/solid/byte_list.go b/cl/cltypes/solid/byte_list.go index 106337e53fb..598bb735e94 100644 --- a/cl/cltypes/solid/byte_list.go +++ b/cl/cltypes/solid/byte_list.go @@ -26,6 +26,7 @@ import ( "github.com/erigontech/erigon/common/clonable" "github.com/erigontech/erigon/common/hexutil" "github.com/erigontech/erigon/common/length" + "github.com/erigontech/erigon/common/math" ) // ByteListSSZ is a variable-length SSZ byte list (ByteList[N]) with a @@ -103,7 +104,7 @@ func (b *ByteListSSZ) HashSSZ() ([32]byte, error) { chunkLimit := (b.limit + 31) / 32 // Pack the data into 32-byte chunks and merkleize with the chunk limit. - leafCount := merkle_tree.NextPowerOfTwo(uint64((len(b.data) + 31) / length.Hash)) + leafCount := math.NextPowerOfTwo(uint64((len(b.data) + 31) / length.Hash)) if leafCount == 0 { leafCount = 1 } diff --git a/cl/cltypes/solid/hash_vector.go b/cl/cltypes/solid/hash_vector.go index 41b2bc4a2c2..af7ed42b8c0 100644 --- a/cl/cltypes/solid/hash_vector.go +++ b/cl/cltypes/solid/hash_vector.go @@ -19,10 +19,10 @@ package solid import ( "encoding/json" - "github.com/erigontech/erigon/cl/merkle_tree" "github.com/erigontech/erigon/common" "github.com/erigontech/erigon/common/clonable" "github.com/erigontech/erigon/common/length" + "github.com/erigontech/erigon/common/math" "github.com/erigontech/erigon/common/ssz" ) @@ -34,7 +34,7 @@ func NewHashVector(s int) HashVectorSSZ { return &hashVector{ u: &hashList{ u: make([]byte, s*length.Hash), - c: int(merkle_tree.NextPowerOfTwo(uint64(s))), + c: int(math.NextPowerOfTwo(uint64(s))), l: s, }, } diff --git a/cl/cltypes/solid/vector.go b/cl/cltypes/solid/vector.go index c3ccb8263de..032a271b6db 100644 --- a/cl/cltypes/solid/vector.go +++ b/cl/cltypes/solid/vector.go @@ -7,6 +7,7 @@ import ( "github.com/erigontech/erigon/cl/merkle_tree" ssz2 "github.com/erigontech/erigon/cl/ssz" "github.com/erigontech/erigon/common/clonable" + "github.com/erigontech/erigon/common/math" "github.com/erigontech/erigon/common/ssz" ) @@ -248,7 +249,7 @@ func (v *VectorSSZ[T]) HashSSZ() ([32]byte, error) { // Initialize MerkleTree if not already done if v.merkleTree == nil { // For vectors, the limit should be the next power of 2 for proper merkleization - limit := merkle_tree.NextPowerOfTwo(uint64(len(v.items))) + limit := math.NextPowerOfTwo(uint64(len(v.items))) v.merkleTree = &merkle_tree.MerkleTree{} v.merkleTree.Initialize(len(v.items), merkle_tree.OptimalMaxTreeCacheDepth, func(idx int, out []byte) { hash, err := v.items[idx].HashSSZ() diff --git a/cl/cltypes/solid/vector_test.go b/cl/cltypes/solid/vector_test.go index 40eeb2c9c7a..24d1573b512 100644 --- a/cl/cltypes/solid/vector_test.go +++ b/cl/cltypes/solid/vector_test.go @@ -22,6 +22,7 @@ import ( "github.com/erigontech/erigon/cl/merkle_tree" "github.com/erigontech/erigon/common" + "github.com/erigontech/erigon/common/math" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -497,7 +498,7 @@ func TestMerkleizeVector_Direct(t *testing.T) { t.Logf("hZero: %x", hZero) // For proper merkleization, length should be next power of 2 - vectorLength := merkle_tree.NextPowerOfTwo(3) // 3 -> 4 + vectorLength := math.NextPowerOfTwo(3) // 3 -> 4 // Test 1: [h1, h1, hZero] leaves1 := [][32]byte{h1, h1, hZero} diff --git a/cl/merkle_tree/merkle_root.go b/cl/merkle_tree/merkle_root.go index ec6b1d9d5dd..5e0c32e84e2 100644 --- a/cl/merkle_tree/merkle_root.go +++ b/cl/merkle_tree/merkle_root.go @@ -28,6 +28,7 @@ import ( "github.com/erigontech/erigon/cl/utils" "github.com/erigontech/erigon/common" "github.com/erigontech/erigon/common/length" + "github.com/erigontech/erigon/common/math" "github.com/erigontech/erigon/common/ssz" ) @@ -44,7 +45,7 @@ func HashTreeRoot(schema ...any) ([32]byte, error) { return [32]byte{}, errors.New("empty schema") } var stack [maxStackLeaves * length.Hash]byte // stack-allocation for most of cases - size := NextPowerOfTwo(uint64(len(schema) * length.Hash)) + size := math.NextPowerOfTwo(uint64(len(schema) * length.Hash)) var leaves []byte if size <= uint64(len(stack)) { leaves = stack[:size] @@ -142,7 +143,7 @@ func MerkleRootFromFlatLeaves(leaves []byte, out []byte) (err error) { copy(out, leaves) return } - return globalHasher.merkleizeTrieLeavesFlat(leaves, out, NextPowerOfTwo(uint64((len(leaves)+31)/32))) + return globalHasher.merkleizeTrieLeavesFlat(leaves, out, math.NextPowerOfTwo(uint64((len(leaves)+31)/32))) } func MerkleRootFromFlatFromIntermediateLevel(nodes []byte, out []byte, leavesLen, intermediateLevel int) (err error) { @@ -150,7 +151,7 @@ func MerkleRootFromFlatFromIntermediateLevel(nodes []byte, out []byte, leavesLen copy(out, nodes) return } - return globalHasher.merkleizeTrieLeavesFlatWithStart(nodes, out, NextPowerOfTwo(uint64((leavesLen+31)/32)), uint64(intermediateLevel)) + return globalHasher.merkleizeTrieLeavesFlatWithStart(nodes, out, math.NextPowerOfTwo(uint64((leavesLen+31)/32)), uint64(intermediateLevel)) } func MerkleRootFromFlatFromIntermediateLevelWithLimit(nodes []byte, out []byte, limit, intermediateLevel int) (err error) { diff --git a/cl/merkle_tree/primitives.go b/cl/merkle_tree/primitives.go index cdea450956b..014daa386f9 100644 --- a/cl/merkle_tree/primitives.go +++ b/cl/merkle_tree/primitives.go @@ -21,6 +21,7 @@ import ( "github.com/erigontech/erigon/common" "github.com/erigontech/erigon/common/length" + "github.com/erigontech/erigon/common/math" ) // Uint64Root retrieves the root hash of a uint64 value by converting it to a byte array and returning it as a hash. @@ -31,7 +32,7 @@ func Uint64Root(val uint64) common.Hash { } func BytesRoot(b []byte) (out [32]byte, err error) { - leafCount := NextPowerOfTwo(uint64((len(b) + 31) / length.Hash)) + leafCount := math.NextPowerOfTwo(uint64((len(b) + 31) / length.Hash)) leaves := make([]byte, leafCount*length.Hash) copy(leaves, b) if err = MerkleRootFromFlatLeaves(leaves, leaves); err != nil { diff --git a/cl/merkle_tree/utils.go b/cl/merkle_tree/utils.go index 5c3b690d704..3ac6cc7e5a6 100644 --- a/cl/merkle_tree/utils.go +++ b/cl/merkle_tree/utils.go @@ -16,23 +16,6 @@ package merkle_tree -func NextPowerOfTwo(n uint64) uint64 { - if n == 0 { - return 1 - } - // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - n-- - n |= n >> 1 - n |= n >> 2 - n |= n >> 4 - n |= n >> 8 - n |= n >> 16 - n |= n >> 32 - n++ - - return n -} - // GetDepth returns the depth of a merkle tree with a given number of nodes. // The depth is defined as the number of levels in the tree, with the root // node at level 0 and each child node at a level one greater than its parent. diff --git a/common/math/integer.go b/common/math/integer.go index dc353bf305c..201f7f3f8f1 100644 --- a/common/math/integer.go +++ b/common/math/integer.go @@ -124,3 +124,12 @@ func SafeAdd(x, y uint64) (uint64, bool) { sum, carryOut := bits.Add64(x, y, 0) return sum, carryOut != 0 } + +// NextPowerOfTwo returns the least power of two at or above n, and 1 for +// n == 0; n above 1<<63 wraps to 0. +func NextPowerOfTwo(n uint64) uint64 { + if n <= 1 { + return 1 + } + return uint64(1) << bits.Len64(n-1) +} diff --git a/common/math/integer_test.go b/common/math/integer_test.go index 3cbe377fb65..ba4f9831e06 100644 --- a/common/math/integer_test.go +++ b/common/math/integer_test.go @@ -89,3 +89,13 @@ func TestAbsoluteDifference(t *testing.T) { assert.Equal(t, AbsoluteDifference(x1, x2), x1-x2) assert.Equal(t, AbsoluteDifference(x2, x1), x1-x2) } + +func TestNextPowerOfTwo(t *testing.T) { + for _, tc := range []struct{ in, want uint64 }{ + {0, 1}, {1, 1}, {2, 2}, {3, 4}, {4, 4}, {5, 8}, + {1<<24 - 1, 1 << 24}, {1 << 24, 1 << 24}, + {1 << 63, 1 << 63}, {1<<63 + 1, 0}, {^uint64(0), 0}, + } { + assert.Equal(t, tc.want, NextPowerOfTwo(tc.in), "n=%d", tc.in) + } +} diff --git a/execution/cache/generic_cache.go b/execution/cache/generic_cache.go index 2dc3435ed37..ff84b931135 100644 --- a/execution/cache/generic_cache.go +++ b/execution/cache/generic_cache.go @@ -18,7 +18,6 @@ package cache import ( "bytes" - "math/bits" "runtime" "sync" "sync/atomic" @@ -31,6 +30,7 @@ import ( "github.com/erigontech/erigon/common/cachebudget" "github.com/erigontech/erigon/common/log/v3" "github.com/erigontech/erigon/common/maphash" + "github.com/erigontech/erigon/common/math" "github.com/erigontech/erigon/execution/cache/coherence" ) @@ -122,17 +122,10 @@ type GenericCache[T any] struct { func u64identity(k uint64) uint32 { return uint32(k) } -func nextPow2(v uint32) uint32 { - if v <= 1 { - return 1 - } - return 1 << bits.Len32(v-1) -} - // initialShardCount starts a lineage at ~64 entries per shard (freelru's own // small-cache geometry), bounded by ceil. func initialShardCount(capacity, ceil uint32) uint32 { - return min(nextPow2(capacity/64), ceil) + return min(uint32(math.NextPowerOfTwo(uint64(capacity/64))), ceil) } const ( @@ -189,7 +182,7 @@ func newGenericCacheEntries[T any](capacityBytes datasize.ByteSize, capacityEntr sizeFunc: sizeFunc, } c.curCap.Store(capacityEntries) - c.shardCeil = nextPow2(uint32(runtime.GOMAXPROCS(0) * 16)) + c.shardCeil = uint32(math.NextPowerOfTwo(uint64(runtime.GOMAXPROCS(0) * 16))) c.shardCount = initialShardCount(capacityEntries, c.shardCeil) // Before any unwind every entry predates the (nonexistent) floor, so all // reads are valid; the floor only drops once an unwind happens. diff --git a/polygon/bor/bor.go b/polygon/bor/bor.go index 000c7d00902..09785b7caeb 100644 --- a/polygon/bor/bor.go +++ b/polygon/bor/bor.go @@ -42,6 +42,7 @@ import ( "github.com/erigontech/erigon/common/empty" "github.com/erigontech/erigon/common/length" "github.com/erigontech/erigon/common/log/v3" + math2 "github.com/erigontech/erigon/common/math" "github.com/erigontech/erigon/db/dbservices" "github.com/erigontech/erigon/db/kv" "github.com/erigontech/erigon/db/rawdb" @@ -1180,7 +1181,7 @@ func (c *Bor) GetRootHash(ctx context.Context, tx kv.Tx, start, end uint64) (str } func ComputeHeadersRootHash(blockHeaders []*types.Header) ([]byte, error) { - headers := make([][32]byte, NextPowerOfTwo(uint64(len(blockHeaders)))) + headers := make([][32]byte, math2.NextPowerOfTwo(uint64(len(blockHeaders)))) for i := range blockHeaders { blockHeader := blockHeaders[i] headers[i] = crypto.Keccak256Hash(AppendBytes32( diff --git a/polygon/bor/merkle.go b/polygon/bor/merkle.go index c9fe45879d3..d137f18489e 100644 --- a/polygon/bor/merkle.go +++ b/polygon/bor/merkle.go @@ -29,23 +29,6 @@ func AppendBytes32(data ...[]byte) []byte { return result } -func NextPowerOfTwo(n uint64) uint64 { - if n == 0 { - return 1 - } - // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 - n-- - n |= n >> 1 - n |= n >> 2 - n |= n >> 4 - n |= n >> 8 - n |= n >> 16 - n |= n >> 32 - n++ - - return n -} - func ConvertTo32(input []byte) (output [32]byte, err error) { l := len(input) if l > 32 || l == 0 {