chore: remove local cache/utils, use external luxfi/cache package

- Remove cache/ and utils/ directories (originally copied from luxfi/node)
- Update secp256k1 imports to use github.com/luxfi/cache/lru
- Update sig_fuzz_test.go to use local crypto/hashing instead of node/utils
- Clean up go.mod: remove unused node/geth dependencies
- crypto package is now fully standalone
This commit is contained in:
Zach Kelling
2025-12-14 21:18:31 -08:00
parent deca5e6954
commit 6fe11f3e77
277 changed files with 5 additions and 23183 deletions
-27
View File
@@ -1,27 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cache
// Cacher acts as a best effort key value store.
type Cacher[K comparable, V any] interface {
// Put inserts an element into the cache. If space is required, elements will
// be evicted.
Put(key K, value V)
// Get returns the entry in the cache with the key specified, if no value
// exists, false is returned.
Get(key K) (V, bool)
// Evict removes the specified entry from the cache
Evict(key K)
// Flush removes all entries from the cache
Flush()
// Returns the number of elements currently in the cache
Len() int
// Returns fraction of cache currently filled (0 --> 1)
PortionFilled() float64
}
-147
View File
@@ -1,147 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cachetest
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/node/cache"
)
const IntSize = ids.IDLen + 8
func IntSizeFunc(ids.ID, int64) int {
return IntSize
}
// Tests is a list of all Cacher tests
var Tests = []struct {
Size int
Func func(t *testing.T, c cache.Cacher[ids.ID, int64])
}{
{Size: 1, Func: Basic},
{Size: 2, Func: Eviction},
}
func Basic(t *testing.T, cache cache.Cacher[ids.ID, int64]) {
require := require.New(t)
id1 := ids.ID{1}
_, found := cache.Get(id1)
require.False(found)
expectedValue1 := int64(1)
cache.Put(id1, expectedValue1)
value, found := cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, value)
cache.Put(id1, expectedValue1)
value, found = cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, value)
cache.Put(id1, expectedValue1)
value, found = cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, value)
id2 := ids.ID{2}
expectedValue2 := int64(2)
cache.Put(id2, expectedValue2)
_, found = cache.Get(id1)
require.False(found)
value, found = cache.Get(id2)
require.True(found)
require.Equal(expectedValue2, value)
}
func Eviction(t *testing.T, cache cache.Cacher[ids.ID, int64]) {
require := require.New(t)
id1 := ids.ID{1}
id2 := ids.ID{2}
id3 := ids.ID{3}
expectedValue1 := int64(1)
expectedValue2 := int64(2)
expectedValue3 := int64(3)
require.Zero(cache.Len())
cache.Put(id1, expectedValue1)
require.Equal(1, cache.Len())
cache.Put(id2, expectedValue2)
require.Equal(2, cache.Len())
val, found := cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, val)
val, found = cache.Get(id2)
require.True(found)
require.Equal(expectedValue2, val)
_, found = cache.Get(id3)
require.False(found)
cache.Put(id3, expectedValue3)
require.Equal(2, cache.Len())
_, found = cache.Get(id1)
require.False(found)
val, found = cache.Get(id2)
require.True(found)
require.Equal(expectedValue2, val)
val, found = cache.Get(id3)
require.True(found)
require.Equal(expectedValue3, val)
cache.Get(id2)
cache.Put(id1, expectedValue1)
val, found = cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, val)
val, found = cache.Get(id2)
require.True(found)
require.Equal(expectedValue2, val)
_, found = cache.Get(id3)
require.False(found)
cache.Evict(id2)
cache.Put(id3, expectedValue3)
val, found = cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, val)
_, found = cache.Get(id2)
require.False(found)
val, found = cache.Get(id3)
require.True(found)
require.Equal(expectedValue3, val)
cache.Flush()
_, found = cache.Get(id1)
require.False(found)
_, found = cache.Get(id2)
require.False(found)
_, found = cache.Get(id3)
require.False(found)
}
-29
View File
@@ -1,29 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cache
import "github.com/luxfi/node/utils"
var _ Cacher[struct{}, struct{}] = (*Empty[struct{}, struct{}])(nil)
// Empty is a cache that doesn't store anything.
type Empty[K any, V any] struct{}
func (*Empty[K, V]) Put(K, V) {}
func (*Empty[K, V]) Get(K) (V, bool) {
return utils.Zero[V](), false
}
func (*Empty[K, _]) Evict(K) {}
func (*Empty[_, _]) Flush() {}
func (*Empty[_, _]) Len() int {
return 0
}
func (*Empty[_, _]) PortionFilled() float64 {
return 0
}
-93
View File
@@ -1,93 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package lru
import (
"sync"
"github.com/luxfi/node/cache"
"github.com/luxfi/node/utils"
"github.com/luxfi/node/utils/linked"
)
var _ cache.Cacher[struct{}, struct{}] = (*Cache[struct{}, struct{}])(nil)
// Cache is a key value store with bounded size. If the size is attempted to be
// exceeded, then an element is removed from the cache before the insertion is
// done, based on evicting the least recently used value.
type Cache[K comparable, V any] struct {
lock sync.Mutex
elements *linked.Hashmap[K, V]
size int
// onEvict is called with the key and value of an entry before eviction.
onEvict func(K, V)
}
func NewCache[K comparable, V any](size int) *Cache[K, V] {
return NewCacheWithOnEvict(size, func(K, V) {})
}
// NewCacheWithOnEvict creates a new LRU cache with the given size and eviction callback.
func NewCacheWithOnEvict[K comparable, V any](size int, onEvict func(K, V)) *Cache[K, V] {
return &Cache[K, V]{
elements: linked.NewHashmap[K, V](),
size: max(size, 1),
onEvict: onEvict,
}
}
func (c *Cache[K, V]) Put(key K, value V) {
c.lock.Lock()
defer c.lock.Unlock()
if c.elements.Len() == c.size {
oldestKey, oldestVal, _ := c.elements.Oldest()
c.elements.Delete(oldestKey)
if c.onEvict != nil {
c.onEvict(oldestKey, oldestVal)
}
}
c.elements.Put(key, value)
}
func (c *Cache[K, V]) Get(key K) (V, bool) {
c.lock.Lock()
defer c.lock.Unlock()
val, ok := c.elements.Get(key)
if !ok {
return utils.Zero[V](), false
}
c.elements.Put(key, val) // Mark [k] as MRU.
return val, true
}
func (c *Cache[K, _]) Evict(key K) {
c.lock.Lock()
defer c.lock.Unlock()
c.elements.Delete(key)
}
func (c *Cache[_, _]) Flush() {
c.lock.Lock()
defer c.lock.Unlock()
c.elements.Clear()
}
func (c *Cache[_, _]) Len() int {
c.lock.Lock()
defer c.lock.Unlock()
return c.elements.Len()
}
func (c *Cache[_, _]) PortionFilled() float64 {
c.lock.Lock()
defer c.lock.Unlock()
return float64(c.elements.Len()) / float64(c.size)
}
-21
View File
@@ -1,21 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package lru
import (
"testing"
"github.com/luxfi/ids"
"github.com/luxfi/node/cache/cachetest"
)
func TestCache(t *testing.T) {
c := NewCache[ids.ID, int64](1)
cachetest.Basic(t, c)
}
func TestCacheEviction(t *testing.T) {
c := NewCache[ids.ID, int64](2)
cachetest.Eviction(t, c)
}
-82
View File
@@ -1,82 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package lru
import (
"sync"
"github.com/luxfi/node/utils/linked"
)
// Evictable allows the object to be notified when it is evicted
//
// Deprecated: Remove this once the vertex state no longer uses it.
type Evictable[K comparable] interface {
Key() K
Evict()
}
// Deduplicator is an LRU cache that notifies the objects when they are evicted.
//
// Deprecated: Remove this once the vertex state no longer uses it.
type Deduplicator[K comparable, V Evictable[K]] struct {
lock sync.Mutex
entryMap map[K]*linked.ListElement[V]
entryList *linked.List[V]
size int
}
// Deprecated: Remove this once the vertex state no longer uses it.
func NewDeduplicator[K comparable, V Evictable[K]](size int) *Deduplicator[K, V] {
return &Deduplicator[K, V]{
entryMap: make(map[K]*linked.ListElement[V]),
entryList: linked.NewList[V](),
size: max(size, 1),
}
}
// Deduplicate returns either the provided value, or a previously provided value
// with the same ID that hasn't yet been evicted
func (d *Deduplicator[_, V]) Deduplicate(value V) V {
d.lock.Lock()
defer d.lock.Unlock()
key := value.Key()
if e, ok := d.entryMap[key]; !ok {
if d.entryList.Len() >= d.size {
e = d.entryList.Front()
d.entryList.MoveToBack(e)
delete(d.entryMap, e.Value.Key())
e.Value.Evict()
e.Value = value
} else {
e = &linked.ListElement[V]{
Value: value,
}
d.entryList.PushBack(e)
}
d.entryMap[key] = e
} else {
d.entryList.MoveToBack(e)
value = e.Value
}
return value
}
// Flush removes all entries from the cache
func (d *Deduplicator[_, _]) Flush() {
d.lock.Lock()
defer d.lock.Unlock()
for d.entryList.Len() > 0 {
e := d.entryList.Front()
d.entryList.Remove(e)
delete(d.entryMap, e.Value.Key())
e.Value.Evict()
}
}
-43
View File
@@ -1,43 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package lru
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
)
type evictable[K comparable] struct {
id K
evicted int
}
func (e *evictable[K]) Key() K {
return e.id
}
func (e *evictable[_]) Evict() {
e.evicted++
}
func TestDeduplicator(t *testing.T) {
require := require.New(t)
cache := NewDeduplicator[ids.ID, *evictable[ids.ID]](1)
expectedValue1 := &evictable[ids.ID]{id: ids.ID{1}}
require.Equal(expectedValue1, cache.Deduplicate(expectedValue1))
require.Zero(expectedValue1.evicted)
require.Equal(expectedValue1, cache.Deduplicate(expectedValue1))
require.Zero(expectedValue1.evicted)
expectedValue2 := &evictable[ids.ID]{id: ids.ID{2}}
returnedValue := cache.Deduplicate(expectedValue2)
require.Equal(expectedValue2, returnedValue)
require.Equal(1, expectedValue1.evicted)
require.Zero(expectedValue2.evicted)
}
-140
View File
@@ -1,140 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package lru
import (
"sync"
"github.com/luxfi/node/cache"
"github.com/luxfi/node/utils"
"github.com/luxfi/node/utils/linked"
)
var _ cache.Cacher[struct{}, any] = (*SizedCache[struct{}, any])(nil)
// sizedElement is used to store the element with its size, so we don't
// calculate the size multiple times.
//
// This ensures that any inconsistencies returned by the size function can not
// corrupt the cache.
type sizedElement[V any] struct {
value V
size int
}
// SizedCache is a key value store with bounded size. If the size is attempted
// to be exceeded, then elements are removed from the cache until the bound is
// honored, based on evicting the least recently used value.
type SizedCache[K comparable, V any] struct {
lock sync.Mutex
elements *linked.Hashmap[K, *sizedElement[V]]
maxSize int
currentSize int
size func(K, V) int
}
func NewSizedCache[K comparable, V any](maxSize int, size func(K, V) int) *SizedCache[K, V] {
return &SizedCache[K, V]{
elements: linked.NewHashmap[K, *sizedElement[V]](),
maxSize: maxSize,
size: size,
}
}
func (c *SizedCache[K, V]) Put(key K, value V) {
c.lock.Lock()
defer c.lock.Unlock()
c.put(key, value)
}
func (c *SizedCache[K, V]) Get(key K) (V, bool) {
c.lock.Lock()
defer c.lock.Unlock()
return c.get(key)
}
func (c *SizedCache[K, V]) Evict(key K) {
c.lock.Lock()
defer c.lock.Unlock()
c.evict(key)
}
func (c *SizedCache[K, V]) Flush() {
c.lock.Lock()
defer c.lock.Unlock()
c.flush()
}
func (c *SizedCache[_, _]) Len() int {
c.lock.Lock()
defer c.lock.Unlock()
return c.len()
}
func (c *SizedCache[_, _]) PortionFilled() float64 {
c.lock.Lock()
defer c.lock.Unlock()
return c.portionFilled()
}
func (c *SizedCache[K, V]) put(key K, value V) {
newEntrySize := c.size(key, value)
if newEntrySize > c.maxSize {
c.flush()
return
}
if oldElement, ok := c.elements.Get(key); ok {
c.currentSize -= oldElement.size
}
// Remove elements until the size of elements in the cache <= [c.maxSize].
for c.currentSize > c.maxSize-newEntrySize {
oldestKey, oldestElement, _ := c.elements.Oldest()
c.elements.Delete(oldestKey)
c.currentSize -= oldestElement.size
}
c.elements.Put(key, &sizedElement[V]{
value: value,
size: newEntrySize,
})
c.currentSize += newEntrySize
}
func (c *SizedCache[K, V]) get(key K) (V, bool) {
element, ok := c.elements.Get(key)
if !ok {
return utils.Zero[V](), false
}
c.elements.Put(key, element) // Mark [k] as MRU.
return element.value, true
}
func (c *SizedCache[K, _]) evict(key K) {
if element, ok := c.elements.Get(key); ok {
c.elements.Delete(key)
c.currentSize -= element.size
}
}
func (c *SizedCache[K, V]) flush() {
c.elements.Clear()
c.currentSize = 0
}
func (c *SizedCache[_, _]) len() int {
return c.elements.Len()
}
func (c *SizedCache[_, _]) portionFilled() float64 {
return float64(c.currentSize) / float64(c.maxSize)
}
-90
View File
@@ -1,90 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package lru
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/node/cache/cachetest"
)
func TestSizedCache(t *testing.T) {
c := NewSizedCache[ids.ID, int64](cachetest.IntSize, cachetest.IntSizeFunc)
cachetest.Basic(t, c)
}
func TestSizedCacheEviction(t *testing.T) {
c := NewSizedCache[ids.ID, int64](2*cachetest.IntSize, cachetest.IntSizeFunc)
cachetest.Eviction(t, c)
}
func TestSizedCacheWrongKeyEvictionRegression(t *testing.T) {
require := require.New(t)
cache := NewSizedCache[string, struct{}](
3,
func(key string, _ struct{}) int {
return len(key)
},
)
cache.Put("a", struct{}{})
cache.Put("b", struct{}{})
cache.Put("c", struct{}{})
cache.Put("dd", struct{}{})
_, ok := cache.Get("a")
require.False(ok)
_, ok = cache.Get("b")
require.False(ok)
_, ok = cache.Get("c")
require.True(ok)
_, ok = cache.Get("dd")
require.True(ok)
}
func TestSizedLRUSizeAlteringRegression(t *testing.T) {
require := require.New(t)
cache := NewSizedCache[string, *string](
5,
func(key string, val *string) int {
if val != nil {
return len(key) + len(*val)
}
return len(key)
},
)
// put first value
expectedPortionFilled := 0.6
valueA := "ab"
cache.Put("a", &valueA)
require.InDelta(expectedPortionFilled, cache.PortionFilled(), 0)
// mutate first value
valueA = "abcd"
require.InDelta(expectedPortionFilled, cache.PortionFilled(), 0, "after value A mutation, portion filled should be the same")
// put second value
expectedPortionFilled = 0.8
valueB := "bcd"
cache.Put("b", &valueB)
require.InDelta(expectedPortionFilled, cache.PortionFilled(), 0)
_, ok := cache.Get("a")
require.False(ok, "key a shouldn't exist after b is put")
_, ok = cache.Get("b")
require.True(ok, "key b should exist")
}
-133
View File
@@ -1,133 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cache
import (
"sync"
"github.com/luxfi/crypto/utils"
"github.com/luxfi/crypto/utils/linked"
)
var _ Cacher[struct{}, struct{}] = (*LRU[struct{}, struct{}])(nil)
// NewLRU creates a new LRU cache with the specified size
func NewLRU[K comparable, V any](size int) *LRU[K, V] {
return &LRU[K, V]{
Size: size,
}
}
// LRU is a key value store with bounded size. If the size is attempted to be
// exceeded, then an element is removed from the cache before the insertion is
// done, based on evicting the least recently used value.
type LRU[K comparable, V any] struct {
lock sync.Mutex
elements *linked.Hashmap[K, V]
// If set to <= 0, will be set internally to 1.
Size int
}
func (c *LRU[K, V]) Put(key K, value V) {
c.lock.Lock()
defer c.lock.Unlock()
c.put(key, value)
}
func (c *LRU[K, V]) Get(key K) (V, bool) {
c.lock.Lock()
defer c.lock.Unlock()
return c.get(key)
}
func (c *LRU[K, _]) Evict(key K) {
c.lock.Lock()
defer c.lock.Unlock()
c.evict(key)
}
func (c *LRU[_, _]) Flush() {
c.lock.Lock()
defer c.lock.Unlock()
c.flush()
}
func (c *LRU[_, _]) Len() int {
c.lock.Lock()
defer c.lock.Unlock()
return c.len()
}
func (c *LRU[_, _]) PortionFilled() float64 {
c.lock.Lock()
defer c.lock.Unlock()
return c.portionFilled()
}
func (c *LRU[K, V]) put(key K, value V) {
c.resize()
if c.elements.Len() == c.Size {
oldestKey, _, _ := c.elements.Oldest()
c.elements.Delete(oldestKey)
}
c.elements.Put(key, value)
}
func (c *LRU[K, V]) get(key K) (V, bool) {
c.resize()
val, ok := c.elements.Get(key)
if !ok {
return utils.Zero[V](), false
}
c.elements.Put(key, val) // Mark [k] as MRU.
return val, true
}
func (c *LRU[K, _]) evict(key K) {
c.resize()
c.elements.Delete(key)
}
func (c *LRU[K, V]) flush() {
if c.elements != nil {
c.elements.Clear()
}
}
func (c *LRU[_, _]) len() int {
if c.elements == nil {
return 0
}
return c.elements.Len()
}
func (c *LRU[_, _]) portionFilled() float64 {
return float64(c.len()) / float64(c.Size)
}
// Initializes [c.elements] if it's nil.
// Sets [c.size] to 1 if it's <= 0.
// Removes oldest elements to make number of elements
// in the cache == [c.size] if necessary.
func (c *LRU[K, V]) resize() {
if c.elements == nil {
c.elements = linked.NewHashmap[K, V]()
}
if c.Size <= 0 {
c.Size = 1
}
for c.elements.Len() > c.Size {
oldestKey, _, _ := c.elements.Oldest()
c.elements.Delete(oldestKey)
}
}
-61
View File
@@ -1,61 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cache
import (
"crypto/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
)
func BenchmarkLRUCachePutSmall(b *testing.B) {
smallLen := 5
cache := &LRU[ids.ID, int]{Size: smallLen}
for n := 0; n < b.N; n++ {
for i := 0; i < smallLen; i++ {
var id ids.ID
_, err := rand.Read(id[:])
require.NoError(b, err)
cache.Put(id, n)
}
b.StopTimer()
cache.Flush()
b.StartTimer()
}
}
func BenchmarkLRUCachePutMedium(b *testing.B) {
mediumLen := 250
cache := &LRU[ids.ID, int]{Size: mediumLen}
for n := 0; n < b.N; n++ {
for i := 0; i < mediumLen; i++ {
var id ids.ID
_, err := rand.Read(id[:])
require.NoError(b, err)
cache.Put(id, n)
}
b.StopTimer()
cache.Flush()
b.StartTimer()
}
}
func BenchmarkLRUCachePutLarge(b *testing.B) {
largeLen := 10000
cache := &LRU[ids.ID, int]{Size: largeLen}
for n := 0; n < b.N; n++ {
for i := 0; i < largeLen; i++ {
var id ids.ID
_, err := rand.Read(id[:])
require.NoError(b, err)
cache.Put(id, n)
}
b.StopTimer()
cache.Flush()
b.StartTimer()
}
}
-67
View File
@@ -1,67 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cache_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/node/cache"
"github.com/luxfi/node/cache/cachetest"
)
func TestLRU(t *testing.T) {
cache := &cache.LRU[ids.ID, int64]{Size: 1}
cachetest.Basic(t, cache)
}
func TestLRUEviction(t *testing.T) {
cache := &cache.LRU[ids.ID, int64]{Size: 2}
cachetest.Eviction(t, cache)
}
func TestLRUResize(t *testing.T) {
require := require.New(t)
cache := cache.LRU[ids.ID, int64]{Size: 2}
id1 := ids.ID{1}
id2 := ids.ID{2}
expectedVal1 := int64(1)
expectedVal2 := int64(2)
cache.Put(id1, expectedVal1)
cache.Put(id2, expectedVal2)
val, found := cache.Get(id1)
require.True(found)
require.Equal(expectedVal1, val)
val, found = cache.Get(id2)
require.True(found)
require.Equal(expectedVal2, val)
cache.Size = 1
// id1 evicted
_, found = cache.Get(id1)
require.False(found)
val, found = cache.Get(id2)
require.True(found)
require.Equal(expectedVal2, val)
cache.Size = 0
// We reset the size to 1 in resize
_, found = cache.Get(id1)
require.False(found)
val, found = cache.Get(id2)
require.True(found)
require.Equal(expectedVal2, val)
}
-126
View File
@@ -1,126 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cache
import (
"sync"
"github.com/luxfi/node/utils"
"github.com/luxfi/node/utils/linked"
)
var _ Cacher[struct{}, any] = (*sizedLRU[struct{}, any])(nil)
// sizedLRU is a key value store with bounded size. If the size is attempted to
// be exceeded, then elements are removed from the cache until the bound is
// honored, based on evicting the least recently used value.
type sizedLRU[K comparable, V any] struct {
lock sync.Mutex
elements *linked.Hashmap[K, V]
maxSize int
currentSize int
size func(K, V) int
}
func NewSizedLRU[K comparable, V any](maxSize int, size func(K, V) int) Cacher[K, V] {
return &sizedLRU[K, V]{
elements: linked.NewHashmap[K, V](),
maxSize: maxSize,
size: size,
}
}
func (c *sizedLRU[K, V]) Put(key K, value V) {
c.lock.Lock()
defer c.lock.Unlock()
c.put(key, value)
}
func (c *sizedLRU[K, V]) Get(key K) (V, bool) {
c.lock.Lock()
defer c.lock.Unlock()
return c.get(key)
}
func (c *sizedLRU[K, V]) Evict(key K) {
c.lock.Lock()
defer c.lock.Unlock()
c.evict(key)
}
func (c *sizedLRU[K, V]) Flush() {
c.lock.Lock()
defer c.lock.Unlock()
c.flush()
}
func (c *sizedLRU[_, _]) Len() int {
c.lock.Lock()
defer c.lock.Unlock()
return c.len()
}
func (c *sizedLRU[_, _]) PortionFilled() float64 {
c.lock.Lock()
defer c.lock.Unlock()
return c.portionFilled()
}
func (c *sizedLRU[K, V]) put(key K, value V) {
newEntrySize := c.size(key, value)
if newEntrySize > c.maxSize {
c.flush()
return
}
if oldValue, ok := c.elements.Get(key); ok {
c.currentSize -= c.size(key, oldValue)
}
// Remove elements until the size of elements in the cache <= [c.maxSize].
for c.currentSize > c.maxSize-newEntrySize {
oldestKey, oldestValue, _ := c.elements.Oldest()
c.elements.Delete(oldestKey)
c.currentSize -= c.size(oldestKey, oldestValue)
}
c.elements.Put(key, value)
c.currentSize += newEntrySize
}
func (c *sizedLRU[K, V]) get(key K) (V, bool) {
value, ok := c.elements.Get(key)
if !ok {
return utils.Zero[V](), false
}
c.elements.Put(key, value) // Mark [k] as MRU.
return value, true
}
func (c *sizedLRU[K, _]) evict(key K) {
if value, ok := c.elements.Get(key); ok {
c.elements.Delete(key)
c.currentSize -= c.size(key, value)
}
}
func (c *sizedLRU[K, V]) flush() {
c.elements.Clear()
c.currentSize = 0
}
func (c *sizedLRU[_, _]) len() int {
return c.elements.Len()
}
func (c *sizedLRU[_, _]) portionFilled() float64 {
return float64(c.currentSize) / float64(c.maxSize)
}
-54
View File
@@ -1,54 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cache_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/node/cache"
"github.com/luxfi/node/cache/cachetest"
)
func TestSizedLRU(t *testing.T) {
c := cache.NewSizedLRU[ids.ID, int64](cachetest.IntSize, cachetest.IntSizeFunc)
cachetest.Basic(t, c)
}
func TestSizedLRUEviction(t *testing.T) {
c := cache.NewSizedLRU[ids.ID, int64](2*cachetest.IntSize, cachetest.IntSizeFunc)
cachetest.Eviction(t, c)
}
func TestSizedLRUWrongKeyEvictionRegression(t *testing.T) {
require := require.New(t)
c := cache.NewSizedLRU[string, struct{}](
3,
func(key string, _ struct{}) int {
return len(key)
},
)
c.Put("a", struct{}{})
c.Put("b", struct{}{})
c.Put("c", struct{}{})
c.Put("dd", struct{}{})
_, ok := c.Get("a")
require.False(ok)
_, ok = c.Get("b")
require.False(ok)
_, ok = c.Get("c")
require.True(ok)
_, ok = c.Get("dd")
require.True(ok)
}
-73
View File
@@ -1,73 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package metercacher
import (
"time"
"github.com/luxfi/metric"
"github.com/luxfi/node/cache"
)
var _ cache.Cacher[struct{}, struct{}] = (*Cache[struct{}, struct{}])(nil)
type Cache[K comparable, V any] struct {
cache.Cacher[K, V]
metrics *cacheMetrics
}
func New[K comparable, V any](
namespace string,
registry metric.Registry,
cache cache.Cacher[K, V],
) (*Cache[K, V], error) {
metrics, err := newMetrics(namespace, registry)
return &Cache[K, V]{
Cacher: cache,
metrics: metrics,
}, err
}
func (c *Cache[K, V]) Put(key K, value V) {
start := time.Now()
c.Cacher.Put(key, value)
putDuration := time.Since(start)
c.metrics.putCount.Inc()
c.metrics.putTime.Add(float64(putDuration))
c.metrics.len.Set(float64(c.Cacher.Len()))
c.metrics.portionFilled.Set(c.Cacher.PortionFilled())
}
func (c *Cache[K, V]) Get(key K) (V, bool) {
start := time.Now()
value, has := c.Cacher.Get(key)
getDuration := time.Since(start)
if has {
c.metrics.getCount.With(hitLabels).Inc()
c.metrics.getTime.With(hitLabels).Add(float64(getDuration))
} else {
c.metrics.getCount.With(missLabels).Inc()
c.metrics.getTime.With(missLabels).Add(float64(getDuration))
}
return value, has
}
func (c *Cache[K, _]) Evict(key K) {
c.Cacher.Evict(key)
c.metrics.len.Set(float64(c.Cacher.Len()))
c.metrics.portionFilled.Set(c.Cacher.PortionFilled())
}
func (c *Cache[_, _]) Flush() {
c.Cacher.Flush()
c.metrics.len.Set(float64(c.Cacher.Len()))
c.metrics.portionFilled.Set(c.Cacher.PortionFilled())
}
-50
View File
@@ -1,50 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build test
package metercacher
import "github.com/luxfi/metric"
import (
"testing"
"github.com/luxfi/metric"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/node/cache"
"github.com/luxfi/node/cache/cachetest"
"github.com/luxfi/node/cache/lru"
)
func TestInterface(t *testing.T) {
scenarios := []struct {
name string
setup func(size int) cache.Cacher[ids.ID, int64]
}{
{
name: "cache LRU",
setup: func(size int) cache.Cacher[ids.ID, int64] {
return lru.NewCache[ids.ID, int64](size)
},
},
{
name: "sized cache LRU",
setup: func(size int) cache.Cacher[ids.ID, int64] {
return lru.NewSizedCache(size*cachetest.IntSize, cachetest.IntSizeFunc)
},
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
for _, test := range cachetest.Tests {
baseCache := scenario.setup(test.Size)
c, err := New("", metric.NewRegistry(), baseCache)
require.NoError(t, err)
test.Func(t, c)
}
})
}
}
-72
View File
@@ -1,72 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package metercacher
import (
"github.com/luxfi/metric"
)
const (
resultLabel = "result"
hitResult = "hit"
missResult = "miss"
)
var (
resultLabels = []string{resultLabel}
hitLabels = metric.Labels{
resultLabel: hitResult,
}
missLabels = metric.Labels{
resultLabel: missResult,
}
)
type cacheMetrics struct {
getCount metric.CounterVec
getTime metric.GaugeVec
putCount metric.Counter
putTime metric.Gauge
len metric.Gauge
portionFilled metric.Gauge
}
func newMetrics(
namespace string,
registry metric.Registry,
) (*cacheMetrics, error) {
metricsInstance := metric.NewWithRegistry(namespace, registry)
m := &cacheMetrics{
getCount: metricsInstance.NewCounterVec(
"get_count",
"number of get calls",
resultLabels,
),
getTime: metricsInstance.NewGaugeVec(
"get_time",
"time spent (ns) in get calls",
resultLabels,
),
putCount: metricsInstance.NewCounter(
"put_count",
"number of put calls",
),
putTime: metricsInstance.NewGauge(
"put_time",
"time spent (ns) in put calls",
),
len: metricsInstance.NewGauge(
"len",
"number of entries",
),
portionFilled: metricsInstance.NewGauge(
"portion_filled",
"fraction of cache filled",
),
}
return m, nil
}
-148
View File
@@ -1,148 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build test
package cache
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
)
const TestIntSize = ids.IDLen + 8
func TestIntSizeFunc(ids.ID, int64) int {
return TestIntSize
}
// CacherTests is a list of all Cacher tests
var CacherTests = []struct {
Size int
Func func(t *testing.T, c Cacher[ids.ID, int64])
}{
{Size: 1, Func: TestBasic},
{Size: 2, Func: TestEviction},
}
func TestBasic(t *testing.T, cache Cacher[ids.ID, int64]) {
require := require.New(t)
id1 := ids.ID{1}
_, found := cache.Get(id1)
require.False(found)
expectedValue1 := int64(1)
cache.Put(id1, expectedValue1)
value, found := cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, value)
cache.Put(id1, expectedValue1)
value, found = cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, value)
cache.Put(id1, expectedValue1)
value, found = cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, value)
id2 := ids.ID{2}
expectedValue2 := int64(2)
cache.Put(id2, expectedValue2)
_, found = cache.Get(id1)
require.False(found)
value, found = cache.Get(id2)
require.True(found)
require.Equal(expectedValue2, value)
}
func TestEviction(t *testing.T, cache Cacher[ids.ID, int64]) {
require := require.New(t)
id1 := ids.ID{1}
id2 := ids.ID{2}
id3 := ids.ID{3}
expectedValue1 := int64(1)
expectedValue2 := int64(2)
expectedValue3 := int64(3)
require.Zero(cache.Len())
cache.Put(id1, expectedValue1)
require.Equal(1, cache.Len())
cache.Put(id2, expectedValue2)
require.Equal(2, cache.Len())
val, found := cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, val)
val, found = cache.Get(id2)
require.True(found)
require.Equal(expectedValue2, val)
_, found = cache.Get(id3)
require.False(found)
cache.Put(id3, expectedValue3)
require.Equal(2, cache.Len())
_, found = cache.Get(id1)
require.False(found)
val, found = cache.Get(id2)
require.True(found)
require.Equal(expectedValue2, val)
val, found = cache.Get(id3)
require.True(found)
require.Equal(expectedValue3, val)
cache.Get(id2)
cache.Put(id1, expectedValue1)
val, found = cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, val)
val, found = cache.Get(id2)
require.True(found)
require.Equal(expectedValue2, val)
_, found = cache.Get(id3)
require.False(found)
cache.Evict(id2)
cache.Put(id3, expectedValue3)
val, found = cache.Get(id1)
require.True(found)
require.Equal(expectedValue1, val)
_, found = cache.Get(id2)
require.False(found)
val, found = cache.Get(id3)
require.True(found)
require.Equal(expectedValue3, val)
cache.Flush()
_, found = cache.Get(id1)
require.False(found)
_, found = cache.Get(id2)
require.False(found)
_, found = cache.Get(id3)
require.False(found)
}
+2 -93
View File
@@ -5,128 +5,37 @@ go 1.25.5
require (
filippo.io/age v1.2.1
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6
github.com/btcsuite/btcd/btcutil v1.1.6
github.com/cloudflare/circl v1.6.2-0.20251204010831-23491bd573cf
github.com/consensys/gnark-crypto v0.19.2
github.com/crate-crypto/go-eth-kzg v1.4.0
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
github.com/ethereum/c-kzg-4844/v2 v2.1.5
github.com/google/btree v1.1.3
github.com/google/gofuzz v1.2.0
github.com/google/renameio/v2 v2.0.0
github.com/gorilla/rpc v1.2.1
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267
github.com/klauspost/compress v1.18.0
github.com/leanovate/gopter v0.2.11
github.com/luxfi/consensus v1.22.26
github.com/luxfi/cache v1.0.0
github.com/luxfi/ids v1.2.4
github.com/luxfi/ledger-lux-go v1.0.0
github.com/luxfi/log v1.1.26
github.com/luxfi/math v1.0.2
github.com/luxfi/metric v1.4.8
github.com/luxfi/mock v0.1.0
github.com/luxfi/node v1.22.14
github.com/mr-tron/base58 v1.2.0
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.11.1
github.com/supranational/blst v0.3.16
github.com/thepudds/fzgen v0.4.3
github.com/zeebo/blake3 v0.2.4
go.uber.org/mock v0.6.0
golang.org/x/crypto v0.46.0
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
golang.org/x/sync v0.19.0
golang.org/x/sys v0.39.0
golang.org/x/tools v0.39.0
gonum.org/v1/gonum v0.16.0
google.golang.org/grpc v1.75.1
)
require (
github.com/DataDog/zstd v1.5.7 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.5 // indirect
github.com/btcsuite/btcutil v1.0.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cockroachdb/errors v1.12.0 // indirect
github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 // indirect
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect
github.com/cockroachdb/pebble v1.1.5 // indirect
github.com/cockroachdb/redact v1.1.6 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgraph-io/badger/v4 v4.8.0 // indirect
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/getsentry/sentry-go v0.35.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/luxfi/cache v1.0.0 // indirect
github.com/luxfi/constants v1.2.2 // indirect
github.com/luxfi/database v1.2.12 // indirect
github.com/luxfi/genesis v1.4.7 // indirect
github.com/luxfi/geth v1.16.53 // indirect
github.com/luxfi/p2p v1.4.6 // indirect
github.com/luxfi/trace v0.1.4 // indirect
github.com/luxfi/utils v1.0.0 // indirect
github.com/luxfi/warp v1.16.36 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/gomega v1.38.0 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.4 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/sanity-io/litter v1.5.5 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zeebo/assert v1.3.0 // indirect
github.com/zondax/golem v0.27.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v1.0.1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/protobuf v1.36.11 // indirect
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
-299
View File
@@ -43,55 +43,17 @@ filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 h1:1zYrtlhrZ6/b6SAjLSfKzWtdgqK0U+HtH/VcBWh1BaU=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY=
github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.3.5 h1:dpAlnAwmT1yIBm3exhT1/8iUSD98RDJM5vqJVQDQLiU=
github.com/btcsuite/btcd/btcec/v2 v2.3.5/go.mod h1:m22FrOAiuxl/tht9wIqAoGHcbnCCaPWyauO8y2LGGtQ=
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00=
github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c=
github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -101,20 +63,6 @@ github.com/cloudflare/circl v1.6.2-0.20251204010831-23491bd573cf/go.mod h1:uddAz
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo=
github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g=
github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 h1:pU88SPhIFid6/k0egdR5V6eALQYq2qbSmukrkgIh/0A=
github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILMqgNeV5jiqR4j+sTuvQNHdf2chuKj1M5k=
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo=
github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw=
github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo=
github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314=
github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb h1:3bCgBvB8PbJVMX1ouCcSIxvsqKPYM7gs72o0zC76n9g=
github.com/cockroachdb/tokenbucket v0.0.0-20250429170803-42689b6311bb/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/consensys/gnark-crypto v0.19.2 h1:qrEAIXq3T4egxqiliFFoNrepkIWVEeIYwt3UL0fvS80=
github.com/consensys/gnark-crypto v0.19.2/go.mod h1:rT23F0XSZqE0mUA0+pRtnL56IbPxs6gp4CeRsBk4XS0=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -122,28 +70,14 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg=
github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs=
github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w=
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -154,30 +88,12 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s=
github.com/ethereum/c-kzg-4844/v2 v2.1.5/go.mod h1:u59hRTTah4Co6i9fDWtiCjTrblJv0UwsqZKCc0GfgUs=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/getsentry/sentry-go v0.35.1 h1:iopow6UVLE2aXu46xKVIs8Z9D/YZkJrHkgozrxa+tOQ=
github.com/getsentry/sentry-go v0.35.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -191,8 +107,6 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -210,17 +124,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -233,8 +138,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -252,27 +155,13 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/rpc v1.2.1 h1:yC+LMV5esttgpVvNORL/xX4jvTTEUE30UZhZ5JF7K9k=
github.com/gorilla/rpc v1.2.1/go.mod h1:uNpOihAlF5xRFLuTYhfR0yfCTm0WTQSQttkMSptRfGk=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -295,24 +184,17 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY=
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -323,44 +205,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
github.com/luxfi/cache v1.0.0 h1:OcqGi/qs9ky+7O8fR6WMZtFsunvA40LBTg3QRQqJurs=
github.com/luxfi/cache v1.0.0/go.mod h1:BEvpffvV3SXs/KSa9nUC1TszxKwUdIHwIaoXpeiYU+Y=
github.com/luxfi/consensus v1.22.26 h1:q80Re+qPdkzYLxp+HLJLutcqM64AWsBC1eaN+XaDbV4=
github.com/luxfi/consensus v1.22.26/go.mod h1:kFwUKSUwzlVpkvL0PJc0NhYMoBn8BSu8qwjlYl0Iizg=
github.com/luxfi/constants v1.2.2 h1:zzmm2CQSrCGqt4SOdHQpS7vZuHXeCgTy4EwK4xtOvbI=
github.com/luxfi/constants v1.2.2/go.mod h1:lJSH4WAMaiVsCC5wi3kptAXJrWiNFwORptpaYTUs7P0=
github.com/luxfi/database v1.2.12 h1:0TWcVlkotLcn9oFUXeHn5zBT24RxnPJLw9LIgapZIzc=
github.com/luxfi/database v1.2.12/go.mod h1:BlczmDTU4z76wPGlhtbzI4iWbEIiWDyfDOCGoq8Cmc0=
github.com/luxfi/genesis v1.4.7 h1:v+2rYwpZXY/2bfaIca8lj7oMueS14fPHHGnJMHr6woY=
github.com/luxfi/genesis v1.4.7/go.mod h1:kns8O17CH6rjVwS+JI5nGzSL7VuqxDArVFVOmj8t07E=
github.com/luxfi/geth v1.16.53 h1:tuOYAUdvVH1BHJWMwv3vzRN/GS+rlI90JrVIDp8DwWA=
github.com/luxfi/geth v1.16.53/go.mod h1:/Rh2kzHGzK1HLd9wA6hnpqDwBev/vN+w6E756EY7Rac=
github.com/luxfi/ids v1.2.4 h1:e0OaeSI6xWjS9JnxxxfvCCs9HHDUrwDc3M9ctIhjcDI=
github.com/luxfi/ids v1.2.4/go.mod h1:HwvRJSNbuZS+u+0JqeHfPX2S13iFyLCnfGxjBCmt244=
github.com/luxfi/ledger-lux-go v1.0.0 h1:u5L6hrJU10tOd3+N6tMkq0ba1uWh/rLXvQiKlJoMENE=
github.com/luxfi/ledger-lux-go v1.0.0/go.mod h1:bJgOxgG1jE5uz8upWrwoDuCS/gfEQEQkL3TMYjwe5VA=
github.com/luxfi/log v1.1.26 h1:ECnJ4wV7TF6WZ5gC6CD6ddZ4OhF+zhQij4bgVUhumDg=
github.com/luxfi/log v1.1.26/go.mod h1:ACN1FtMlJ/NRuWROYH2TtiFmJuUbSG+OxINlRzJM/38=
github.com/luxfi/math v1.0.2 h1:bgBGmVC7S/QxLCgAJZJdvjFHMNXygcApcKofIn7bWpU=
github.com/luxfi/math v1.0.2/go.mod h1:2Jk1/s0f0yN2q0Sx/cBExbFJz7IDEbjxlP3VUiaujms=
github.com/luxfi/metric v1.4.8 h1:mMbcJW+E1z6qQxn34+BE109HlvT6ciiDXuc10qEBJRs=
github.com/luxfi/metric v1.4.8/go.mod h1:AUQ7NSNz9WndAcr/SKnOkP7XSFFnBXOa+ihtJYfDaQY=
github.com/luxfi/mock v0.1.0 h1:IwElfNu+T9sXvzFX6tudPDx1vqPuACRSRdxpD5lxW+o=
github.com/luxfi/mock v0.1.0/go.mod h1:izF+9K0gGzFC9zERn6Po37v46eLdPB+EIsDjL3GLk+U=
github.com/luxfi/node v1.22.14 h1:hravEERCTm9/W6pRZyLJ+c97K/HHwHIK86X4+NOwLsk=
github.com/luxfi/node v1.22.14/go.mod h1:uAAABB2ny8xOGueJGw3euXA9nadf73100qj+Ei2hiG0=
github.com/luxfi/p2p v1.4.6 h1:Xxu69AfFOGwMvtwDCbaf4bpRS3bvj57xRwBgqxbICdM=
github.com/luxfi/p2p v1.4.6/go.mod h1:i3nwxhknQ86tsM7UC+QlQXHL11gIucd45os0jMKKIEg=
github.com/luxfi/trace v0.1.4 h1:ttCRyXGwWuz232se+lIUqhWHBoTuvPLhHH/hLWyqtaM=
github.com/luxfi/trace v0.1.4/go.mod h1:Az7HWh+PCuPftXjQu+ssjv51nKauaFu+q2un7bmZYBA=
github.com/luxfi/utils v1.0.0 h1:eibxyw/xkwnB9KRSmYvKLbdLTUElyfJpxLIBeTBKqlU=
github.com/luxfi/utils v1.0.0/go.mod h1:ABqhBdGNig0CaDcnNYldv1byS0BTNi5IKPbJSPF1p98=
github.com/luxfi/warp v1.16.36 h1:zznGGx91vYI06MBG441jE7wzd3VSMGXERiGVTtN2KJI=
github.com/luxfi/warp v1.16.36/go.mod h1:zXX6/GErHux4xaMy7nV8c4sdFQLFX6iva08kBxGPylo=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -378,70 +230,24 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY=
github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -451,8 +257,6 @@ github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
@@ -460,55 +264,29 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/supranational/blst v0.3.16 h1:bTDadT+3fK497EvLdWRQEjiGnUtzJ7jjIUMF0jqwYhE=
github.com/supranational/blst v0.3.16/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
github.com/thepudds/fzgen v0.4.3 h1:srUP/34BulQaEwPP/uHZkdjUcUjIzL7Jkf4CBVryiP8=
github.com/thepudds/fzgen v0.4.3/go.mod h1:BhhwtRhzgvLWAjjcHDJ9pEiLD2Z9hrVIFjBCHJ//zJ4=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw=
github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
github.com/zondax/ledger-go v1.0.1 h1:Ks/2tz/dOF+dbRynfZ0dEhcdL1lqw43Sa0zMXHpQ3aQ=
github.com/zondax/ledger-go v1.0.1/go.mod h1:j7IgMY39f30apthJYMd1YsHZRqdyu4KbVmUp0nU78X0=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
@@ -519,47 +297,21 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -602,16 +354,11 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -634,11 +381,9 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -648,15 +393,9 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -686,7 +425,6 @@ golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -696,12 +434,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -715,16 +450,13 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -732,16 +464,11 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
@@ -757,17 +484,12 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -815,25 +537,18 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -904,10 +619,6 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1:DMCgtIAIQGZqJXMVzJF4MV8BlWoJh2ZuFiRdAleyr58=
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -928,8 +639,6 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -942,25 +651,17 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"math/big"
"strings"
"github.com/luxfi/crypto/cache/lru"
"github.com/luxfi/cache/lru"
"github.com/luxfi/crypto/cb58"
"github.com/luxfi/crypto/hashing"
"github.com/luxfi/ids"
+1 -1
View File
@@ -4,7 +4,7 @@
package secp256k1
import (
"github.com/luxfi/crypto/cache/lru"
"github.com/luxfi/cache/lru"
)
// RecoverCacheType provides a cache for public key recovery with methods
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"math/big"
"testing"
"github.com/luxfi/node/utils/hashing"
"github.com/luxfi/crypto/hashing"
)
// FuzzSignatureVerification tests signature verification with random inputs
-63
View File
@@ -1,63 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package utils
import (
"encoding/json"
"sync"
)
var (
_ json.Marshaler = (*Atomic[struct{}])(nil)
_ json.Unmarshaler = (*Atomic[struct{}])(nil)
)
type Atomic[T any] struct {
lock sync.RWMutex
value T
}
func NewAtomic[T any](value T) *Atomic[T] {
return &Atomic[T]{
value: value,
}
}
func (a *Atomic[T]) Get() T {
a.lock.RLock()
defer a.lock.RUnlock()
return a.value
}
func (a *Atomic[T]) Set(value T) {
a.lock.Lock()
defer a.lock.Unlock()
a.value = value
}
func (a *Atomic[T]) Swap(value T) T {
a.lock.Lock()
defer a.lock.Unlock()
old := a.value
a.value = value
return old
}
func (a *Atomic[T]) MarshalJSON() ([]byte, error) {
a.lock.RLock()
defer a.lock.RUnlock()
return json.Marshal(a.value)
}
func (a *Atomic[T]) UnmarshalJSON(b []byte) error {
a.lock.Lock()
defer a.lock.Unlock()
return json.Unmarshal(b, &a.value)
}
-71
View File
@@ -1,71 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package utils
import (
"encoding/json"
"net/netip"
"testing"
"github.com/stretchr/testify/require"
)
func TestAtomic(t *testing.T) {
require := require.New(t)
var a Atomic[bool]
require.Zero(a.Get())
a.Set(false)
require.False(a.Get())
a.Set(true)
require.True(a.Get())
a.Set(false)
require.False(a.Get())
}
func TestAtomicJSON(t *testing.T) {
tests := []struct {
name string
value *Atomic[netip.AddrPort]
expected string
}{
{
name: "zero value",
value: new(Atomic[netip.AddrPort]),
expected: `""`,
},
{
name: "ipv4 value",
value: NewAtomic(netip.AddrPortFrom(
netip.AddrFrom4([4]byte{1, 2, 3, 4}),
12345,
)),
expected: `"1.2.3.4:12345"`,
},
{
name: "ipv6 loopback",
value: NewAtomic(netip.AddrPortFrom(
netip.IPv6Loopback(),
12345,
)),
expected: `"[::1]:12345"`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
b, err := json.Marshal(test.value)
require.NoError(err)
require.Equal(test.expected, string(b))
var parsed Atomic[netip.AddrPort]
require.NoError(json.Unmarshal([]byte(test.expected), &parsed))
require.Equal(test.value.Get(), parsed.Get())
})
}
}
-37
View File
@@ -1,37 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package beacon
import (
"net/netip"
"github.com/luxfi/ids"
)
var _ Beacon = (*beacon)(nil)
type Beacon interface {
ID() ids.NodeID
IP() netip.AddrPort
}
type beacon struct {
id ids.NodeID
ip netip.AddrPort
}
func New(id ids.NodeID, ip netip.AddrPort) Beacon {
return &beacon{
id: id,
ip: ip,
}
}
func (b *beacon) ID() ids.NodeID {
return b.id
}
func (b *beacon) IP() netip.AddrPort {
return b.ip
}
-132
View File
@@ -1,132 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package beacon
import (
"errors"
"net/netip"
"strings"
"github.com/luxfi/ids"
)
var (
_ Set = (*set)(nil)
errDuplicateID = errors.New("duplicated ID")
errDuplicateIP = errors.New("duplicated IP")
errUnknownID = errors.New("unknown ID")
errUnknownIP = errors.New("unknown IP")
)
type Set interface {
Add(Beacon) error
RemoveByID(ids.NodeID) error
RemoveByIP(netip.AddrPort) error
Len() int
IDsArg() string
IPsArg() string
}
type set struct {
ids map[ids.NodeID]int
ips map[netip.AddrPort]int
beacons []Beacon
}
func NewSet() Set {
return &set{
ids: make(map[ids.NodeID]int),
ips: make(map[netip.AddrPort]int),
}
}
func (s *set) Add(b Beacon) error {
id := b.ID()
_, duplicateID := s.ids[id]
if duplicateID {
return errDuplicateID
}
ip := b.IP()
_, duplicateIP := s.ips[ip]
if duplicateIP {
return errDuplicateIP
}
s.ids[id] = len(s.beacons)
s.ips[ip] = len(s.beacons)
s.beacons = append(s.beacons, b)
return nil
}
func (s *set) RemoveByID(idToRemove ids.NodeID) error {
indexToRemove, exists := s.ids[idToRemove]
if !exists {
return errUnknownID
}
toRemove := s.beacons[indexToRemove]
ipToRemove := toRemove.IP()
indexToMove := len(s.beacons) - 1
toMove := s.beacons[indexToMove]
idToMove := toMove.ID()
ipToMove := toMove.IP()
s.ids[idToMove] = indexToRemove
s.ips[ipToMove] = indexToRemove
s.beacons[indexToRemove] = toMove
delete(s.ids, idToRemove)
delete(s.ips, ipToRemove)
s.beacons[indexToMove] = nil
s.beacons = s.beacons[:indexToMove]
return nil
}
func (s *set) RemoveByIP(ip netip.AddrPort) error {
indexToRemove, exists := s.ips[ip]
if !exists {
return errUnknownIP
}
toRemove := s.beacons[indexToRemove]
idToRemove := toRemove.ID()
return s.RemoveByID(idToRemove)
}
func (s *set) Len() int {
return len(s.beacons)
}
func (s *set) IDsArg() string {
sb := strings.Builder{}
if len(s.beacons) == 0 {
return ""
}
b := s.beacons[0]
_, _ = sb.WriteString(b.ID().String())
for _, b := range s.beacons[1:] {
_, _ = sb.WriteString(",")
_, _ = sb.WriteString(b.ID().String())
}
return sb.String()
}
func (s *set) IPsArg() string {
sb := strings.Builder{}
if len(s.beacons) == 0 {
return ""
}
b := s.beacons[0]
_, _ = sb.WriteString(b.IP().String())
for _, b := range s.beacons[1:] {
_, _ = sb.WriteString(",")
_, _ = sb.WriteString(b.IP().String())
}
return sb.String()
}
-81
View File
@@ -1,81 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package beacon
import (
"net/netip"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
)
func TestSet(t *testing.T) {
require := require.New(t)
id0 := ids.BuildTestNodeID([]byte{0})
id1 := ids.BuildTestNodeID([]byte{1})
id2 := ids.BuildTestNodeID([]byte{2})
ip0 := netip.AddrPortFrom(
netip.IPv4Unspecified(),
0,
)
ip1 := netip.AddrPortFrom(
netip.IPv4Unspecified(),
1,
)
ip2 := netip.AddrPortFrom(
netip.IPv4Unspecified(),
2,
)
b0 := New(id0, ip0)
b1 := New(id1, ip1)
b2 := New(id2, ip2)
s := NewSet()
require.Empty(s.IDsArg())
require.Empty(s.IPsArg())
require.Zero(s.Len())
require.NoError(s.Add(b0))
require.Equal("NodeID-111111111111111111116DBWJs", s.IDsArg())
require.Equal("0.0.0.0:0", s.IPsArg())
require.Equal(1, s.Len())
err := s.Add(b0)
require.ErrorIs(err, errDuplicateID)
require.Equal("NodeID-111111111111111111116DBWJs", s.IDsArg())
require.Equal("0.0.0.0:0", s.IPsArg())
require.Equal(1, s.Len())
require.NoError(s.Add(b1))
require.Equal("NodeID-111111111111111111116DBWJs,NodeID-6HgC8KRBEhXYbF4riJyJFLSHt37UNuRt", s.IDsArg())
require.Equal("0.0.0.0:0,0.0.0.0:1", s.IPsArg())
require.Equal(2, s.Len())
require.NoError(s.Add(b2))
require.Equal("NodeID-111111111111111111116DBWJs,NodeID-6HgC8KRBEhXYbF4riJyJFLSHt37UNuRt,NodeID-BaMPFdqMUQ46BV8iRcwbVfsam55kMqcp", s.IDsArg())
require.Equal("0.0.0.0:0,0.0.0.0:1,0.0.0.0:2", s.IPsArg())
require.Equal(3, s.Len())
require.NoError(s.RemoveByID(b0.ID()))
require.Equal("NodeID-BaMPFdqMUQ46BV8iRcwbVfsam55kMqcp,NodeID-6HgC8KRBEhXYbF4riJyJFLSHt37UNuRt", s.IDsArg())
require.Equal("0.0.0.0:2,0.0.0.0:1", s.IPsArg())
require.Equal(2, s.Len())
require.NoError(s.RemoveByIP(b1.IP()))
require.Equal("NodeID-BaMPFdqMUQ46BV8iRcwbVfsam55kMqcp", s.IDsArg())
require.Equal("0.0.0.0:2", s.IPsArg())
require.Equal(1, s.Len())
}
-154
View File
@@ -1,154 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bimap
import (
"bytes"
"encoding/json"
"errors"
"golang.org/x/exp/maps"
"github.com/luxfi/node/utils"
)
var (
_ json.Marshaler = (*BiMap[int, int])(nil)
_ json.Unmarshaler = (*BiMap[int, int])(nil)
nullBytes = []byte("null")
errNotBijective = errors.New("map not bijective")
)
type Entry[K, V any] struct {
Key K
Value V
}
// BiMap is a bi-directional map.
type BiMap[K, V comparable] struct {
keyToValue map[K]V
valueToKey map[V]K
}
// New creates a new empty bimap.
func New[K, V comparable]() *BiMap[K, V] {
return &BiMap[K, V]{
keyToValue: make(map[K]V),
valueToKey: make(map[V]K),
}
}
// Put the key value pair into the map. If either [key] or [val] was previously
// in the map, the previous entries will be removed and returned.
//
// Note: Unlike normal maps, it's possible that Put removes 0, 1, or 2 existing
// entries to ensure that mappings are one-to-one.
func (m *BiMap[K, V]) Put(key K, val V) []Entry[K, V] {
var removed []Entry[K, V]
oldVal, oldValDeleted := m.DeleteKey(key)
if oldValDeleted {
removed = append(removed, Entry[K, V]{
Key: key,
Value: oldVal,
})
}
oldKey, oldKeyDeleted := m.DeleteValue(val)
if oldKeyDeleted {
removed = append(removed, Entry[K, V]{
Key: oldKey,
Value: val,
})
}
m.keyToValue[key] = val
m.valueToKey[val] = key
return removed
}
// GetKey that maps to the provided value.
func (m *BiMap[K, V]) GetKey(val V) (K, bool) {
key, ok := m.valueToKey[val]
return key, ok
}
// GetValue that is mapped to the provided key.
func (m *BiMap[K, V]) GetValue(key K) (V, bool) {
val, ok := m.keyToValue[key]
return val, ok
}
// HasKey returns true if [key] is in the map.
func (m *BiMap[K, _]) HasKey(key K) bool {
_, ok := m.keyToValue[key]
return ok
}
// HasValue returns true if [val] is in the map.
func (m *BiMap[_, V]) HasValue(val V) bool {
_, ok := m.valueToKey[val]
return ok
}
// DeleteKey removes [key] from the map and returns the value it mapped to.
func (m *BiMap[K, V]) DeleteKey(key K) (V, bool) {
val, ok := m.keyToValue[key]
if !ok {
return utils.Zero[V](), false
}
delete(m.keyToValue, key)
delete(m.valueToKey, val)
return val, true
}
// DeleteValue removes [val] from the map and returns the key that mapped to it.
func (m *BiMap[K, V]) DeleteValue(val V) (K, bool) {
key, ok := m.valueToKey[val]
if !ok {
return utils.Zero[K](), false
}
delete(m.keyToValue, key)
delete(m.valueToKey, val)
return key, true
}
// Keys returns the keys of the map. The keys will be in an indeterminate order.
func (m *BiMap[K, _]) Keys() []K {
return maps.Keys(m.keyToValue)
}
// Values returns the values of the map. The values will be in an indeterminate
// order.
func (m *BiMap[_, V]) Values() []V {
return maps.Values(m.keyToValue)
}
// Len return the number of entries in this map.
func (m *BiMap[K, V]) Len() int {
return len(m.keyToValue)
}
func (m *BiMap[K, V]) MarshalJSON() ([]byte, error) {
return json.Marshal(m.keyToValue)
}
func (m *BiMap[K, V]) UnmarshalJSON(b []byte) error {
if bytes.Equal(b, nullBytes) {
return nil
}
var keyToValue map[K]V
if err := json.Unmarshal(b, &keyToValue); err != nil {
return err
}
valueToKey := make(map[V]K, len(keyToValue))
for k, v := range keyToValue {
valueToKey[v] = k
}
if len(keyToValue) != len(valueToKey) {
return errNotBijective
}
m.keyToValue = keyToValue
m.valueToKey = valueToKey
return nil
}
-366
View File
@@ -1,366 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bimap
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestBiMapPut(t *testing.T) {
tests := []struct {
name string
state *BiMap[int, int]
key int
value int
expectedRemoved []Entry[int, int]
expectedState *BiMap[int, int]
}{
{
name: "none removed",
state: New[int, int](),
key: 1,
value: 2,
expectedRemoved: nil,
expectedState: &BiMap[int, int]{
keyToValue: map[int]int{
1: 2,
},
valueToKey: map[int]int{
2: 1,
},
},
},
{
name: "key removed",
state: &BiMap[int, int]{
keyToValue: map[int]int{
1: 2,
},
valueToKey: map[int]int{
2: 1,
},
},
key: 1,
value: 3,
expectedRemoved: []Entry[int, int]{
{
Key: 1,
Value: 2,
},
},
expectedState: &BiMap[int, int]{
keyToValue: map[int]int{
1: 3,
},
valueToKey: map[int]int{
3: 1,
},
},
},
{
name: "value removed",
state: &BiMap[int, int]{
keyToValue: map[int]int{
1: 2,
},
valueToKey: map[int]int{
2: 1,
},
},
key: 3,
value: 2,
expectedRemoved: []Entry[int, int]{
{
Key: 1,
Value: 2,
},
},
expectedState: &BiMap[int, int]{
keyToValue: map[int]int{
3: 2,
},
valueToKey: map[int]int{
2: 3,
},
},
},
{
name: "key and value removed",
state: &BiMap[int, int]{
keyToValue: map[int]int{
1: 2,
3: 4,
},
valueToKey: map[int]int{
2: 1,
4: 3,
},
},
key: 1,
value: 4,
expectedRemoved: []Entry[int, int]{
{
Key: 1,
Value: 2,
},
{
Key: 3,
Value: 4,
},
},
expectedState: &BiMap[int, int]{
keyToValue: map[int]int{
1: 4,
},
valueToKey: map[int]int{
4: 1,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
removed := test.state.Put(test.key, test.value)
require.Equal(test.expectedRemoved, removed)
require.Equal(test.expectedState, test.state)
})
}
}
func TestBiMapHasValueAndGetKey(t *testing.T) {
m := New[int, int]()
require.Empty(t, m.Put(1, 2))
tests := []struct {
name string
value int
expectedKey int
expectedExists bool
}{
{
name: "fetch unknown",
value: 3,
expectedKey: 0,
expectedExists: false,
},
{
name: "fetch known value",
value: 2,
expectedKey: 1,
expectedExists: true,
},
{
name: "fetch known key",
value: 1,
expectedKey: 0,
expectedExists: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
exists := m.HasValue(test.value)
require.Equal(test.expectedExists, exists)
key, exists := m.GetKey(test.value)
require.Equal(test.expectedKey, key)
require.Equal(test.expectedExists, exists)
})
}
}
func TestBiMapHasKeyAndGetValue(t *testing.T) {
m := New[int, int]()
require.Empty(t, m.Put(1, 2))
tests := []struct {
name string
key int
expectedValue int
expectedExists bool
}{
{
name: "fetch unknown",
key: 3,
expectedValue: 0,
expectedExists: false,
},
{
name: "fetch known key",
key: 1,
expectedValue: 2,
expectedExists: true,
},
{
name: "fetch known value",
key: 2,
expectedValue: 0,
expectedExists: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
exists := m.HasKey(test.key)
require.Equal(test.expectedExists, exists)
value, exists := m.GetValue(test.key)
require.Equal(test.expectedValue, value)
require.Equal(test.expectedExists, exists)
})
}
}
func TestBiMapDeleteKey(t *testing.T) {
tests := []struct {
name string
state *BiMap[int, int]
key int
expectedValue int
expectedRemoved bool
expectedState *BiMap[int, int]
}{
{
name: "none removed",
state: New[int, int](),
key: 1,
expectedValue: 0,
expectedRemoved: false,
expectedState: New[int, int](),
},
{
name: "key removed",
state: &BiMap[int, int]{
keyToValue: map[int]int{
1: 2,
},
valueToKey: map[int]int{
2: 1,
},
},
key: 1,
expectedValue: 2,
expectedRemoved: true,
expectedState: New[int, int](),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
value, removed := test.state.DeleteKey(test.key)
require.Equal(test.expectedValue, value)
require.Equal(test.expectedRemoved, removed)
require.Equal(test.expectedState, test.state)
})
}
}
func TestBiMapDeleteValue(t *testing.T) {
tests := []struct {
name string
state *BiMap[int, int]
value int
expectedKey int
expectedRemoved bool
expectedState *BiMap[int, int]
}{
{
name: "none removed",
state: New[int, int](),
value: 1,
expectedKey: 0,
expectedRemoved: false,
expectedState: New[int, int](),
},
{
name: "key removed",
state: &BiMap[int, int]{
keyToValue: map[int]int{
1: 2,
},
valueToKey: map[int]int{
2: 1,
},
},
value: 2,
expectedKey: 1,
expectedRemoved: true,
expectedState: New[int, int](),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
key, removed := test.state.DeleteValue(test.value)
require.Equal(test.expectedKey, key)
require.Equal(test.expectedRemoved, removed)
require.Equal(test.expectedState, test.state)
})
}
}
func TestBiMapLenAndLists(t *testing.T) {
require := require.New(t)
m := New[int, int]()
require.Zero(m.Len())
require.Empty(m.Keys())
require.Empty(m.Values())
m.Put(1, 2)
require.Equal(1, m.Len())
require.ElementsMatch([]int{1}, m.Keys())
require.ElementsMatch([]int{2}, m.Values())
m.Put(2, 3)
require.Equal(2, m.Len())
require.ElementsMatch([]int{1, 2}, m.Keys())
require.ElementsMatch([]int{2, 3}, m.Values())
m.Put(1, 3)
require.Equal(1, m.Len())
require.ElementsMatch([]int{1}, m.Keys())
require.ElementsMatch([]int{3}, m.Values())
m.DeleteKey(1)
require.Zero(m.Len())
require.Empty(m.Keys())
require.Empty(m.Values())
}
func TestBiMapJSON(t *testing.T) {
require := require.New(t)
expectedMap := New[int, int]()
expectedMap.Put(1, 2)
expectedMap.Put(2, 3)
jsonBytes, err := json.Marshal(expectedMap)
require.NoError(err)
expectedJSONBytes := []byte(`{"1":2,"2":3}`)
require.JSONEq(string(expectedJSONBytes), string(jsonBytes))
var unmarshalledMap BiMap[int, int]
require.NoError(json.Unmarshal(jsonBytes, &unmarshalledMap))
require.Equal(expectedMap, &unmarshalledMap)
}
func TestBiMapInvalidJSON(t *testing.T) {
require := require.New(t)
invalidJSONBytes := []byte(`{"1":2,"2":2}`)
var unmarshalledMap BiMap[int, int]
err := json.Unmarshal(invalidJSONBytes, &unmarshalledMap)
require.ErrorIs(err, errNotBijective)
}
-32
View File
@@ -1,32 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestNew(t *testing.T) {
var (
require = require.New(t)
count = 10000
p = 0.1
)
numHashes, numEntries := OptimalParameters(count, p)
f, err := New(numHashes, numEntries)
require.NoError(err)
require.NotNil(f)
salt := []byte("test salt")
Add(f, []byte("hello"), salt)
contains := Contains(f, []byte("hello"), salt)
require.True(contains, "should have contained the key")
contains = Contains(f, []byte("bye"), salt)
require.False(contains, "shouldn't have contained the key")
}
-14
View File
@@ -1,14 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
// BloomFilter is the interface for bloom filter implementations that support
// adding and checking raw byte slices
type BloomFilter interface {
// Add adds to filter, assumed thread safe
Add(...[]byte)
// Check checks filter, assumed thread safe
Check([]byte) bool
}
-147
View File
@@ -1,147 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"math/bits"
"sync"
)
const (
minHashes = 1
maxHashes = 16 // Supports a false positive probability of 2^-16 when using optimal size values
minEntries = 1
bitsPerByte = 8
bytesPerUint64 = 8
hashRotation = 17
)
var (
errInvalidNumHashes = errors.New("invalid num hashes")
errTooFewHashes = errors.New("too few hashes")
errTooManyHashes = errors.New("too many hashes")
errTooFewEntries = errors.New("too few entries")
)
type Filter struct {
// numBits is always equal to [bitsPerByte * len(entries)]
numBits uint64
lock sync.RWMutex
hashSeeds []uint64
entries []byte
count int
}
// New creates a new Filter with the specified number of hashes and bytes for
// entries. The returned bloom filter is safe for concurrent usage.
func New(numHashes, numEntries int) (*Filter, error) {
if numEntries < minEntries {
return nil, errTooFewEntries
}
hashSeeds, err := newHashSeeds(numHashes)
if err != nil {
return nil, err
}
return &Filter{
numBits: uint64(numEntries * bitsPerByte),
hashSeeds: hashSeeds,
entries: make([]byte, numEntries),
count: 0,
}, nil
}
func (f *Filter) Add(hash uint64) {
f.lock.Lock()
defer f.lock.Unlock()
_ = 1 % f.numBits // hint to the compiler that numBits is not 0
for _, seed := range f.hashSeeds {
hash = bits.RotateLeft64(hash, hashRotation) ^ seed
index := hash % f.numBits
byteIndex := index / bitsPerByte
bitIndex := index % bitsPerByte
f.entries[byteIndex] |= 1 << bitIndex
}
f.count++
}
// Count returns the number of elements that have been added to the bloom
// filter.
func (f *Filter) Count() int {
f.lock.RLock()
defer f.lock.RUnlock()
return f.count
}
func (f *Filter) Contains(hash uint64) bool {
f.lock.RLock()
defer f.lock.RUnlock()
return contains(f.hashSeeds, f.entries, hash)
}
func (f *Filter) Marshal() []byte {
f.lock.RLock()
defer f.lock.RUnlock()
return marshal(f.hashSeeds, f.entries)
}
func newHashSeeds(count int) ([]uint64, error) {
switch {
case count < minHashes:
return nil, fmt.Errorf("%w: %d < %d", errTooFewHashes, count, minHashes)
case count > maxHashes:
return nil, fmt.Errorf("%w: %d > %d", errTooManyHashes, count, maxHashes)
}
bytes := make([]byte, count*bytesPerUint64)
if _, err := rand.Reader.Read(bytes); err != nil {
return nil, err
}
seeds := make([]uint64, count)
for i := range seeds {
seeds[i] = binary.BigEndian.Uint64(bytes[i*bytesPerUint64:])
}
return seeds, nil
}
func contains(hashSeeds []uint64, entries []byte, hash uint64) bool {
var (
numBits = bitsPerByte * uint64(len(entries))
_ = 1 % numBits // hint to the compiler that numBits is not 0
accumulator byte = 1
)
for seedIndex := 0; seedIndex < len(hashSeeds) && accumulator != 0; seedIndex++ {
hash = bits.RotateLeft64(hash, hashRotation) ^ hashSeeds[seedIndex]
index := hash % numBits
byteIndex := index / bitsPerByte
bitIndex := index % bitsPerByte
accumulator &= entries[byteIndex] >> bitIndex
}
return accumulator != 0
}
func marshal(hashSeeds []uint64, entries []byte) []byte {
numHashes := len(hashSeeds)
entriesOffset := 1 + numHashes*bytesPerUint64
bytes := make([]byte, entriesOffset+len(entries))
bytes[0] = byte(numHashes)
for i, seed := range hashSeeds {
binary.BigEndian.PutUint64(bytes[1+i*bytesPerUint64:], seed)
}
copy(bytes[entriesOffset:], entries)
return bytes
}
-71
View File
@@ -1,71 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"encoding/binary"
"github.com/spaolacci/murmur3"
)
// marshalCommon serializes [hashSeeds] and [entries] into a byte slice.
func marshalCommon(hashSeeds []uint64, entries []byte) []byte {
bytes := make([]byte, 1+len(hashSeeds)*bytesPerUint64+len(entries))
bytes[0] = byte(len(hashSeeds))
offset := 1
for _, seed := range hashSeeds {
binary.BigEndian.PutUint64(bytes[offset:], seed)
offset += bytesPerUint64
}
copy(bytes[offset:], entries)
return bytes
}
// containsCommon returns true if [hash] is in the filter defined by [hashSeeds] and [entries].
func containsCommon(hashSeeds []uint64, entries []byte, hash uint64) bool {
for _, seed := range hashSeeds {
if !containsWithSeed(entries, hash, seed) {
return false
}
}
return true
}
func containsWithSeed(entries []byte, hash, seed uint64) bool {
index := getIndex(entries, hash, seed)
byteIndex := index / bitsPerByte
bitIndex := index % bitsPerByte
return entries[byteIndex]&(1<<bitIndex) != 0
}
func getIndex(entries []byte, hash, seed uint64) uint64 {
// If the filter has L entries, we only want to use hashes in the range
// [0, L). We achieve this by incrementally shifting the hash to use
// bits that we haven't used before. If we run out of bits, we
// perform a "hash extension" by rehashing the original hash and the
// seed.
//
// If the size of the bloom filter approaches MaxUint64, the hash may
// overflow back to the beginning of the range. This means that the
// same index can be used more than once for a single value.
// This is OK, it just means the filter isn't as good as it could be
// if we had more bits in the hash.
numEntries := uint64(len(entries))
entriesMask := numEntries*bitsPerByte - 1
hash += seed
// Note: It may be significantly more performant to use:
// https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
// over the modulo operation.
index := hash & entriesMask
for index >= numEntries*bitsPerByte {
hash = hash >> hashRotation
// If the hash is zero, we have run out of bits and need to
// rehash.
if hash == 0 {
hash = uint64(murmur3.Sum64(binary.BigEndian.AppendUint64(binary.BigEndian.AppendUint64(nil, hash), seed)))
}
index = hash & entriesMask
}
return index
}
-96
View File
@@ -1,96 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"math/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/utils/units"
)
func TestNewErrors(t *testing.T) {
tests := []struct {
numHashes int
numEntries int
err error
}{
{
numHashes: 0,
numEntries: 1,
err: errTooFewHashes,
},
{
numHashes: 17,
numEntries: 1,
err: errTooManyHashes,
},
{
numHashes: 8,
numEntries: 0,
err: errTooFewEntries,
},
}
for _, test := range tests {
t.Run(test.err.Error(), func(t *testing.T) {
_, err := New(test.numHashes, test.numEntries)
require.ErrorIs(t, err, test.err)
})
}
}
func TestNormalUsage(t *testing.T) {
require := require.New(t)
toAdd := make([]uint64, 1024)
for i := range toAdd {
toAdd[i] = rand.Uint64() //#nosec G404
}
initialNumHashes, initialNumBytes := OptimalParameters(1024, 0.01)
filter, err := New(initialNumHashes, initialNumBytes)
require.NoError(err)
for i, elem := range toAdd {
filter.Add(elem)
for _, elem := range toAdd[:i] {
require.True(filter.Contains(elem))
}
}
require.Equal(len(toAdd), filter.Count())
filterBytes := filter.Marshal()
parsedFilter, err := Parse(filterBytes)
require.NoError(err)
for _, elem := range toAdd {
require.True(parsedFilter.Contains(elem))
}
parsedFilterBytes := parsedFilter.Marshal()
require.Equal(filterBytes, parsedFilterBytes)
}
func BenchmarkAdd(b *testing.B) {
f, err := New(8, 16*units.KiB)
require.NoError(b, err)
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Add(1)
}
}
func BenchmarkMarshal(b *testing.B) {
f, err := New(OptimalParameters(10_000, .01))
require.NoError(b, err)
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Marshal()
}
}
-31
View File
@@ -1,31 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"crypto/sha256"
"encoding/binary"
)
func Add(f *Filter, key, salt []byte) {
f.Add(Hash(key, salt))
}
func Contains(c Checker, key, salt []byte) bool {
return c.Contains(Hash(key, salt))
}
type Checker interface {
Contains(hash uint64) bool
}
func Hash(key, salt []byte) uint64 {
hash := sha256.New()
// sha256.Write never returns errors
_, _ = hash.Write(key)
_, _ = hash.Write(salt)
output := make([]byte, 0, sha256.Size)
return binary.BigEndian.Uint64(hash.Sum(output))
}
-34
View File
@@ -1,34 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/node/utils/units"
)
func TestCollisionResistance(t *testing.T) {
require := require.New(t)
f, err := New(8, 16*units.KiB)
require.NoError(err)
Add(f, []byte("hello world?"), []byte("so salty"))
collision := Contains(f, []byte("hello world!"), []byte("so salty"))
require.False(collision)
}
func BenchmarkHash(b *testing.B) {
key := ids.GenerateTestID()
salt := ids.GenerateTestID()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Hash(key[:], salt[:])
}
}
-35
View File
@@ -1,35 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"sync"
"github.com/luxfi/math/set"
)
type mapFilter struct {
lock sync.RWMutex
values set.Set[string]
}
func NewMap() BloomFilter {
return &mapFilter{}
}
func (m *mapFilter) Add(bl ...[]byte) {
m.lock.Lock()
defer m.lock.Unlock()
for _, b := range bl {
m.values.Add(string(b))
}
}
func (m *mapFilter) Check(b []byte) bool {
m.lock.RLock()
defer m.lock.RUnlock()
return m.values.Contains(string(b))
}
-58
View File
@@ -1,58 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"github.com/luxfi/metric"
)
// Metrics is a collection of commonly useful metrics when using a long-lived
// bloom filter.
type Metrics struct {
Count metric.Gauge
NumHashes metric.Gauge
NumEntries metric.Gauge
MaxCount metric.Gauge
ResetCount metric.Counter
}
func NewMetrics(
namespace string,
registry metric.Registry,
) (*Metrics, error) {
metricsInstance := metric.NewWithRegistry(namespace, registry)
m := &Metrics{
Count: metricsInstance.NewGauge(
"count",
"Number of additions that have been performed to the bloom",
),
NumHashes: metricsInstance.NewGauge(
"hashes",
"Number of hashes in the bloom",
),
NumEntries: metricsInstance.NewGauge(
"entries",
"Number of bytes allocated to slots in the bloom",
),
MaxCount: metricsInstance.NewGauge(
"max_count",
"Maximum number of additions that should be performed to the bloom before resetting",
),
ResetCount: metricsInstance.NewCounter(
"reset_count",
"Number times the bloom has been reset",
),
}
return m, nil
}
// Reset the metrics to align with the provided bloom filter and max count.
func (m *Metrics) Reset(newFilter *Filter, maxCount int) {
m.Count.Set(float64(newFilter.Count()))
m.NumHashes.Set(float64(len(newFilter.hashSeeds)))
m.NumEntries.Set(float64(len(newFilter.entries)))
m.MaxCount.Set(float64(maxCount))
m.ResetCount.Inc()
}
-111
View File
@@ -1,111 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import "math"
const ln2Squared = math.Ln2 * math.Ln2
// OptimalParameters calculates the optimal [numHashes] and [numEntries] that
// should be allocated for a bloom filter which will contain [count] and target
// [falsePositiveProbability].
func OptimalParameters(count int, falsePositiveProbability float64) (int, int) {
numEntries := OptimalEntries(count, falsePositiveProbability)
numHashes := OptimalHashes(numEntries, count)
return numHashes, numEntries
}
// OptimalHashes calculates the number of hashes which will minimize the false
// positive probability of a bloom filter with [numEntries] after [count]
// additions.
//
// It is guaranteed to return a value in the range [minHashes, maxHashes].
//
// ref: https://en.wikipedia.org/wiki/Bloom_filter
func OptimalHashes(numEntries, count int) int {
switch {
case numEntries < minEntries:
return minHashes
case count <= 0:
return maxHashes
}
numHashes := math.Ceil(float64(numEntries) * bitsPerByte * math.Ln2 / float64(count))
// Converting a floating-point value to an int produces an undefined value
// if the floating-point value cannot be represented as an int. To avoid
// this undefined behavior, we explicitly check against MaxInt here.
//
// ref: https://go.dev/ref/spec#Conversions
if numHashes >= maxHashes {
return maxHashes
}
return max(int(numHashes), minHashes)
}
// OptimalEntries calculates the optimal number of entries to use when creating
// a new Bloom filter when targenting a size of [count] with
// [falsePositiveProbability] assuming that the optimal number of hashes is
// used.
//
// It is guaranteed to return a value in the range [minEntries, MaxInt].
//
// ref: https://en.wikipedia.org/wiki/Bloom_filter
func OptimalEntries(count int, falsePositiveProbability float64) int {
switch {
case count <= 0:
return minEntries
case falsePositiveProbability >= 1:
return minEntries
case falsePositiveProbability <= 0:
return math.MaxInt
}
entriesInBits := -float64(count) * math.Log(falsePositiveProbability) / ln2Squared
entries := (entriesInBits + bitsPerByte - 1) / bitsPerByte
// Converting a floating-point value to an int produces an undefined value
// if the floating-point value cannot be represented as an int. To avoid
// this undefined behavior, we explicitly check against MaxInt here.
//
// ref: https://go.dev/ref/spec#Conversions
if entries >= math.MaxInt {
return math.MaxInt
}
return max(int(entries), minEntries)
}
// EstimateCount estimates the number of additions a bloom filter with
// [numHashes] and [numEntries] must have to reach [falsePositiveProbability].
// This is derived by inversing a lower-bound on the probability of false
// positives. For values where numBits >> numHashes, the predicted probability
// is fairly accurate.
//
// It is guaranteed to return a value in the range [0, MaxInt].
//
// ref: https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=903775
func EstimateCount(numHashes, numEntries int, falsePositiveProbability float64) int {
switch {
case numHashes < minHashes:
return 0
case numEntries < minEntries:
return 0
case falsePositiveProbability <= 0:
return 0
case falsePositiveProbability >= 1:
return math.MaxInt
}
invNumHashes := 1 / float64(numHashes)
numBits := float64(numEntries * 8)
exp := 1 - math.Pow(falsePositiveProbability, invNumHashes)
count := math.Ceil(-math.Log(exp) * numBits * invNumHashes)
// Converting a floating-point value to an int produces an undefined value
// if the floating-point value cannot be represented as an int. To avoid
// this undefined behavior, we explicitly check against MaxInt here.
//
// ref: https://go.dev/ref/spec#Conversions
if count >= math.MaxInt {
return math.MaxInt
}
return int(count)
}
-203
View File
@@ -1,203 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"fmt"
"math"
"testing"
"github.com/stretchr/testify/require"
)
const largestFloat64LessThan1 float64 = 1 - 1e-16
func TestOptimalHashes(t *testing.T) {
tests := []struct {
numEntries int
count int
expectedHashes int
}{
{ // invalid params
numEntries: 0,
count: 1024,
expectedHashes: minHashes,
},
{ // invalid params
numEntries: 1024,
count: 0,
expectedHashes: maxHashes,
},
{
numEntries: math.MaxInt,
count: 1,
expectedHashes: maxHashes,
},
{
numEntries: 1,
count: math.MaxInt,
expectedHashes: minHashes,
},
{
numEntries: 1024,
count: 1024,
expectedHashes: 6,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%d_%d", test.numEntries, test.count), func(t *testing.T) {
hashes := OptimalHashes(test.numEntries, test.count)
require.Equal(t, test.expectedHashes, hashes)
})
}
}
func TestOptimalEntries(t *testing.T) {
tests := []struct {
count int
falsePositiveProbability float64
expectedEntries int
}{
{ // invalid params
count: 0,
falsePositiveProbability: .5,
expectedEntries: minEntries,
},
{ // invalid params
count: 1,
falsePositiveProbability: 0,
expectedEntries: math.MaxInt,
},
{ // invalid params
count: 1,
falsePositiveProbability: 1,
expectedEntries: minEntries,
},
{
count: math.MaxInt,
falsePositiveProbability: math.SmallestNonzeroFloat64,
expectedEntries: math.MaxInt,
},
{
count: 1024,
falsePositiveProbability: largestFloat64LessThan1,
expectedEntries: minEntries,
},
{
count: 1024,
falsePositiveProbability: .01,
expectedEntries: 1227,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%d_%f", test.count, test.falsePositiveProbability), func(t *testing.T) {
entries := OptimalEntries(test.count, test.falsePositiveProbability)
require.Equal(t, test.expectedEntries, entries)
})
}
}
func TestEstimateEntries(t *testing.T) {
tests := []struct {
numHashes int
numEntries int
falsePositiveProbability float64
expectedEntries int
}{
{ // invalid params
numHashes: 0,
numEntries: 2_048,
falsePositiveProbability: .5,
expectedEntries: 0,
},
{ // invalid params
numHashes: 1,
numEntries: 0,
falsePositiveProbability: .5,
expectedEntries: 0,
},
{ // invalid params
numHashes: 1,
numEntries: 1,
falsePositiveProbability: 2,
expectedEntries: math.MaxInt,
},
{ // invalid params
numHashes: 1,
numEntries: 1,
falsePositiveProbability: -1,
expectedEntries: 0,
},
{
numHashes: 8,
numEntries: 2_048,
falsePositiveProbability: 0,
expectedEntries: 0,
},
{ // params from OptimalParameters(10_000, .01)
numHashes: 7,
numEntries: 11_982,
falsePositiveProbability: .01,
expectedEntries: 9_993,
},
{ // params from OptimalParameters(100_000, .001)
numHashes: 10,
numEntries: 179_720,
falsePositiveProbability: .001,
expectedEntries: 100_000,
},
{ // params from OptimalParameters(10_000, .01)
numHashes: 7,
numEntries: 11_982,
falsePositiveProbability: .05,
expectedEntries: 14_449,
},
{ // params from OptimalParameters(10_000, .01)
numHashes: 7,
numEntries: 11_982,
falsePositiveProbability: 1,
expectedEntries: math.MaxInt,
},
{ // params from OptimalParameters(10_000, .01)
numHashes: 7,
numEntries: 11_982,
falsePositiveProbability: math.SmallestNonzeroFloat64,
expectedEntries: 0,
},
{ // params from OptimalParameters(10_000, .01)
numHashes: 7,
numEntries: 11_982,
falsePositiveProbability: largestFloat64LessThan1,
expectedEntries: math.MaxInt,
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%d_%d_%f", test.numHashes, test.numEntries, test.falsePositiveProbability), func(t *testing.T) {
entries := EstimateCount(test.numHashes, test.numEntries, test.falsePositiveProbability)
require.Equal(t, test.expectedEntries, entries)
})
}
}
func FuzzOptimalHashes(f *testing.F) {
f.Fuzz(func(t *testing.T, numEntries, count int) {
hashes := OptimalHashes(numEntries, count)
require.GreaterOrEqual(t, hashes, minHashes)
require.LessOrEqual(t, hashes, maxHashes)
})
}
func FuzzOptimalEntries(f *testing.F) {
f.Fuzz(func(t *testing.T, count int, falsePositiveProbability float64) {
entries := OptimalEntries(count, falsePositiveProbability)
require.GreaterOrEqual(t, entries, minEntries)
})
}
func FuzzEstimateEntries(f *testing.F) {
f.Fuzz(func(t *testing.T, numHashes, numEntries int, falsePositiveProbability float64) {
entries := EstimateCount(numHashes, numEntries, falsePositiveProbability)
require.GreaterOrEqual(t, entries, 0)
})
}
-65
View File
@@ -1,65 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"encoding/binary"
"fmt"
)
var (
EmptyFilter = &ReadFilter{
hashSeeds: make([]uint64, minHashes),
entries: make([]byte, minEntries),
}
FullFilter = &ReadFilter{
hashSeeds: make([]uint64, minHashes),
entries: make([]byte, minEntries),
}
)
func init() {
for i := range FullFilter.entries {
FullFilter.entries[i] = 0xFF
}
}
type ReadFilter struct {
hashSeeds []uint64
entries []byte
}
// Parse [bytes] into a read-only bloom filter.
func Parse(bytes []byte) (*ReadFilter, error) {
if len(bytes) == 0 {
return nil, errInvalidNumHashes
}
numHashes := bytes[0]
entriesOffset := 1 + int(numHashes)*bytesPerUint64
switch {
case numHashes < minHashes:
return nil, fmt.Errorf("%w: %d < %d", errTooFewHashes, numHashes, minHashes)
case numHashes > maxHashes:
return nil, fmt.Errorf("%w: %d > %d", errTooManyHashes, numHashes, maxHashes)
case len(bytes) < entriesOffset+minEntries: // numEntries = len(bytes) - entriesOffset
return nil, errTooFewEntries
}
f := &ReadFilter{
hashSeeds: make([]uint64, numHashes),
entries: bytes[entriesOffset:],
}
for i := range f.hashSeeds {
f.hashSeeds[i] = binary.BigEndian.Uint64(bytes[1+i*bytesPerUint64:])
}
return f, nil
}
func (f *ReadFilter) Contains(hash uint64) bool {
return contains(f.hashSeeds, f.entries, hash)
}
func (f *ReadFilter) Marshal() []byte {
return marshal(f.hashSeeds, f.entries)
}
-112
View File
@@ -1,112 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"math"
"testing"
"github.com/stretchr/testify/require"
)
func NewMaliciousFilter(numHashes, numEntries int) *Filter {
f := &Filter{
numBits: uint64(numEntries * bitsPerByte),
hashSeeds: make([]uint64, numHashes),
entries: make([]byte, numEntries),
count: 0,
}
for i := range f.entries {
f.entries[i] = math.MaxUint8
}
return f
}
func TestParseErrors(t *testing.T) {
tests := []struct {
bytes []byte
err error
}{
{
bytes: nil,
err: errInvalidNumHashes,
},
{
bytes: NewMaliciousFilter(0, 1).Marshal(),
err: errTooFewHashes,
},
{
bytes: NewMaliciousFilter(17, 1).Marshal(),
err: errTooManyHashes,
},
{
bytes: NewMaliciousFilter(1, 0).Marshal(),
err: errTooFewEntries,
},
{
bytes: []byte{
0x01, // num hashes = 1
},
err: errTooFewEntries,
},
}
for _, test := range tests {
t.Run(test.err.Error(), func(t *testing.T) {
_, err := Parse(test.bytes)
require.ErrorIs(t, err, test.err)
})
}
}
func BenchmarkParse(b *testing.B) {
f, err := New(OptimalParameters(10_000, .01))
require.NoError(b, err)
bytes := f.Marshal()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = Parse(bytes)
}
}
func BenchmarkContains(b *testing.B) {
f := NewMaliciousFilter(maxHashes, 1)
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Contains(1)
}
}
func FuzzParseThenMarshal(f *testing.F) {
f.Fuzz(func(t *testing.T, bytes []byte) {
f, err := Parse(bytes)
if err != nil {
return
}
marshalledBytes := marshal(f.hashSeeds, f.entries)
require.Equal(t, bytes, marshalledBytes)
})
}
func FuzzMarshalThenParse(f *testing.F) {
f.Fuzz(func(t *testing.T, numHashes int, entries []byte) {
require := require.New(t)
hashSeeds, err := newHashSeeds(numHashes)
if err != nil {
return
}
if len(entries) < minEntries {
return
}
marshalledBytes := marshal(hashSeeds, entries)
rf, err := Parse(marshalledBytes)
require.NoError(err)
require.Equal(hashSeeds, rf.hashSeeds)
require.Equal(entries, rf.entries)
})
}
-90
View File
@@ -1,90 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package buffer
import "errors"
var (
_ Queue[struct{}] = (*boundedQueue[struct{}])(nil)
errInvalidMaxSize = errors.New("maxSize must be greater than 0")
)
// A FIFO queue.
type Queue[T any] interface {
// Pushes [elt] onto the queue.
// If the queue is full, the oldest element is evicted to make space.
Push(T)
// Pops the oldest element from the queue.
// Returns false if the queue is empty.
Pop() (T, bool)
// Returns the oldest element without removing it.
// Returns false if the queue is empty.
Peek() (T, bool)
// Returns the element at the given index without removing it.
// Index(0) returns the oldest element.
// Index(Len() - 1) returns the newest element.
// Returns false if there is no element at that index.
Index(int) (T, bool)
// Returns the number of elements in the queue.
Len() int
// Returns the queue elements from oldest to newest.
// This is an O(n) operation and should be used sparingly.
List() []T
}
// Keeps up to [maxSize] entries in an ordered buffer
// and calls [onEvict] on any item that is evicted.
// Not safe for concurrent use.
type boundedQueue[T any] struct {
deque Deque[T]
maxSize int
onEvict func(T)
}
// Returns a new bounded, non-blocking queue that holds up to [maxSize] elements.
// When an element is evicted, [onEvict] is called with the evicted element.
// If [onEvict] is nil, this is a no-op.
// [maxSize] must be >= 1.
// Not safe for concurrent use.
func NewBoundedQueue[T any](maxSize int, onEvict func(T)) (Queue[T], error) {
if maxSize < 1 {
return nil, errInvalidMaxSize
}
return &boundedQueue[T]{
deque: NewUnboundedDeque[T](maxSize + 1), // +1 so we never resize
maxSize: maxSize,
onEvict: onEvict,
}, nil
}
func (b *boundedQueue[T]) Push(elt T) {
if b.deque.Len() == b.maxSize {
evicted, _ := b.deque.PopLeft()
if b.onEvict != nil {
b.onEvict(evicted)
}
}
_ = b.deque.PushRight(elt)
}
func (b *boundedQueue[T]) Pop() (T, bool) {
return b.deque.PopLeft()
}
func (b *boundedQueue[T]) Peek() (T, bool) {
return b.deque.PeekLeft()
}
func (b *boundedQueue[T]) Index(i int) (T, bool) {
return b.deque.Index(i)
}
func (b *boundedQueue[T]) Len() int {
return b.deque.Len()
}
func (b *boundedQueue[T]) List() []T {
return b.deque.List()
}
@@ -1,142 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package buffer
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestNewBoundedQueue(t *testing.T) {
require := require.New(t)
// Case: maxSize < 1
_, err := NewBoundedQueue[bool](0, nil)
require.ErrorIs(err, errInvalidMaxSize)
// Case: maxSize == 1 and nil onEvict
b, err := NewBoundedQueue[bool](1, nil)
require.NoError(err)
// Put 2 elements to make sure we don't panic on evict
b.Push(true)
b.Push(true)
}
func TestBoundedQueue(t *testing.T) {
require := require.New(t)
maxSize := 3
evicted := []int{}
onEvict := func(elt int) {
evicted = append(evicted, elt)
}
b, err := NewBoundedQueue(maxSize, onEvict)
require.NoError(err)
require.Zero(b.Len())
// Fill the queue
for i := 0; i < maxSize; i++ {
b.Push(i)
require.Equal(i+1, b.Len())
got, ok := b.Peek()
require.True(ok)
require.Zero(got)
got, ok = b.Index(i)
require.True(ok)
require.Equal(i, got)
require.Len(b.List(), i+1)
}
require.Equal([]int{}, evicted)
require.Len(b.List(), maxSize)
// Queue is [0, 1, 2]
// Empty the queue
for i := 0; i < maxSize; i++ {
got, ok := b.Pop()
require.True(ok)
require.Equal(i, got)
require.Equal(maxSize-i-1, b.Len())
require.Len(b.List(), maxSize-i-1)
}
// Queue is empty
_, ok := b.Pop()
require.False(ok)
_, ok = b.Peek()
require.False(ok)
_, ok = b.Index(0)
require.False(ok)
require.Zero(b.Len())
require.Empty(b.List())
// Fill the queue again
for i := 0; i < maxSize; i++ {
b.Push(i)
require.Equal(i+1, b.Len())
}
// Queue is [0, 1, 2]
// Putting another element should evict the oldest.
b.Push(maxSize)
// Queue is [1, 2, 3]
require.Equal(maxSize, b.Len())
require.Len(b.List(), maxSize)
got, ok := b.Peek()
require.True(ok)
require.Equal(1, got)
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.Index(maxSize - 1)
require.True(ok)
require.Equal(maxSize, got)
require.Equal([]int{0}, evicted)
// Put 2 more elements
b.Push(maxSize + 1)
b.Push(maxSize + 2)
// Queue is [3, 4, 5]
require.Equal(maxSize, b.Len())
require.Equal([]int{0, 1, 2}, evicted)
got, ok = b.Peek()
require.True(ok)
require.Equal(3, got)
require.Equal([]int{3, 4, 5}, b.List())
for i := maxSize; i < 2*maxSize; i++ {
got, ok := b.Index(i - maxSize)
require.True(ok)
require.Equal(i, got)
}
// Empty the queue
for i := 0; i < maxSize; i++ {
got, ok := b.Pop()
require.True(ok)
require.Equal(i+3, got)
require.Equal(maxSize-i-1, b.Len())
require.Len(b.List(), maxSize-i-1)
}
// Queue is empty
require.Empty(b.List())
require.Zero(b.Len())
require.Equal([]int{0, 1, 2}, evicted)
_, ok = b.Pop()
require.False(ok)
_, ok = b.Peek()
require.False(ok)
_, ok = b.Index(0)
require.False(ok)
}
-169
View File
@@ -1,169 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package buffer
import (
"sync"
"github.com/luxfi/node/utils"
)
var _ BlockingDeque[int] = (*unboundedBlockingDeque[int])(nil)
type BlockingDeque[T any] interface {
Deque[T]
// Close and empty the deque.
Close()
}
// Returns a new unbounded deque with the given initial size.
// Note that the returned deque is always empty -- [initSize] is just
// a hint to prevent unnecessary resizing.
func NewUnboundedBlockingDeque[T any](initSize int) BlockingDeque[T] {
q := &unboundedBlockingDeque[T]{
Deque: NewUnboundedDeque[T](initSize),
}
q.cond = sync.NewCond(&q.lock)
return q
}
type unboundedBlockingDeque[T any] struct {
lock sync.RWMutex
cond *sync.Cond
closed bool
Deque[T]
}
// If the deque is closed returns false.
func (q *unboundedBlockingDeque[T]) PushRight(elt T) bool {
q.cond.L.Lock()
defer q.cond.L.Unlock()
if q.closed {
return false
}
// Add the item to the queue
q.Deque.PushRight(elt)
// Signal a waiting thread
q.cond.Signal()
return true
}
// If the deque is closed returns false.
func (q *unboundedBlockingDeque[T]) PopRight() (T, bool) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
for {
if q.closed {
return utils.Zero[T](), false
}
if q.Deque.Len() != 0 {
return q.Deque.PopRight()
}
q.cond.Wait()
}
}
func (q *unboundedBlockingDeque[T]) PeekRight() (T, bool) {
q.lock.RLock()
defer q.lock.RUnlock()
if q.closed {
return utils.Zero[T](), false
}
return q.Deque.PeekRight()
}
// If the deque is closed returns false.
func (q *unboundedBlockingDeque[T]) PushLeft(elt T) bool {
q.cond.L.Lock()
defer q.cond.L.Unlock()
if q.closed {
return false
}
// Add the item to the queue
q.Deque.PushLeft(elt)
// Signal a waiting thread
q.cond.Signal()
return true
}
// If the deque is closed returns false.
func (q *unboundedBlockingDeque[T]) PopLeft() (T, bool) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
for {
if q.closed {
return utils.Zero[T](), false
}
if q.Deque.Len() != 0 {
return q.Deque.PopLeft()
}
q.cond.Wait()
}
}
func (q *unboundedBlockingDeque[T]) PeekLeft() (T, bool) {
q.lock.RLock()
defer q.lock.RUnlock()
if q.closed {
return utils.Zero[T](), false
}
return q.Deque.PeekLeft()
}
func (q *unboundedBlockingDeque[T]) Index(i int) (T, bool) {
q.lock.RLock()
defer q.lock.RUnlock()
if q.closed {
return utils.Zero[T](), false
}
return q.Deque.Index(i)
}
func (q *unboundedBlockingDeque[T]) Len() int {
q.lock.RLock()
defer q.lock.RUnlock()
if q.closed {
return 0
}
return q.Deque.Len()
}
func (q *unboundedBlockingDeque[T]) List() []T {
q.lock.RLock()
defer q.lock.RUnlock()
if q.closed {
return nil
}
return q.Deque.List()
}
func (q *unboundedBlockingDeque[T]) Close() {
q.cond.L.Lock()
defer q.cond.L.Unlock()
if q.closed {
return
}
q.Deque = nil
// Mark the queue as closed
q.closed = true
q.cond.Broadcast()
}
@@ -1,106 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package buffer
import (
"sync"
"testing"
"github.com/stretchr/testify/require"
)
func TestUnboundedBlockingDequePush(t *testing.T) {
require := require.New(t)
deque := NewUnboundedBlockingDeque[int](2)
require.Empty(deque.List())
_, ok := deque.Index(0)
require.False(ok)
ok = deque.PushRight(1)
require.True(ok)
require.Equal([]int{1}, deque.List())
got, ok := deque.Index(0)
require.True(ok)
require.Equal(1, got)
ok = deque.PushRight(2)
require.True(ok)
require.Equal([]int{1, 2}, deque.List())
got, ok = deque.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = deque.Index(1)
require.True(ok)
require.Equal(2, got)
_, ok = deque.Index(2)
require.False(ok)
ch, ok := deque.PopLeft()
require.True(ok)
require.Equal(1, ch)
require.Equal([]int{2}, deque.List())
got, ok = deque.Index(0)
require.True(ok)
require.Equal(2, got)
}
func TestUnboundedBlockingDequePop(t *testing.T) {
require := require.New(t)
deque := NewUnboundedBlockingDeque[int](2)
require.Empty(deque.List())
ok := deque.PushRight(1)
require.True(ok)
require.Equal([]int{1}, deque.List())
got, ok := deque.Index(0)
require.True(ok)
require.Equal(1, got)
ch, ok := deque.PopLeft()
require.True(ok)
require.Equal(1, ch)
require.Empty(deque.List())
var (
gotOk bool
gotCh int
)
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
gotCh, gotOk = deque.PopLeft()
wg.Done()
}()
ok = deque.PushRight(2)
require.True(ok)
wg.Wait()
require.True(gotOk)
require.Equal(2, gotCh)
require.Empty(deque.List())
_, ok = deque.Index(0)
require.False(ok)
}
func TestUnboundedBlockingDequeClose(t *testing.T) {
require := require.New(t)
deque := NewUnboundedBlockingDeque[int](2)
ok := deque.PushLeft(1)
require.True(ok)
deque.Close()
_, ok = deque.PopRight()
require.False(ok)
ok = deque.PushLeft(1)
require.False(ok)
}
-190
View File
@@ -1,190 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package buffer
import "github.com/luxfi/node/utils"
const defaultInitSize = 32
// An unbounded deque (double-ended queue).
// See https://en.wikipedia.org/wiki/Double-ended_queue
// Not safe for concurrent access.
type Deque[T any] interface {
// Place an element at the leftmost end of the deque.
// Returns true if the element was placed in the deque.
PushLeft(T) bool
// Place an element at the rightmost end of the deque.
// Returns true if the element was placed in the deque.
PushRight(T) bool
// Remove and return the leftmost element of the deque.
// Returns false if the deque is empty.
PopLeft() (T, bool)
// Remove and return the rightmost element of the deque.
// Returns false if the deque is empty.
PopRight() (T, bool)
// Return the leftmost element of the deque without removing it.
// Returns false if the deque is empty.
PeekLeft() (T, bool)
// Return the rightmost element of the deque without removing it.
// Returns false if the deque is empty.
PeekRight() (T, bool)
// Returns the element at the given index.
// Returns false if the index is out of bounds.
// The leftmost element is at index 0.
Index(int) (T, bool)
// Returns the number of elements in the deque.
Len() int
// Returns the elements in the deque from left to right.
List() []T
}
// Returns a new unbounded deque with the given initial slice size.
// Note that the returned deque is always empty -- [initSize] is just
// a hint to prevent unnecessary resizing.
func NewUnboundedDeque[T any](initSize int) Deque[T] {
if initSize < 2 {
initSize = defaultInitSize
}
return &unboundedSliceDeque[T]{
// Note that [initSize] must be >= 2 to satisfy invariants (1) and (2).
data: make([]T, initSize),
right: 1,
}
}
// Invariants after each function call and before the first call:
// (1) The next element pushed left will be placed at data[left]
// (2) The next element pushed right will be placed at data[right]
// (3) There are [size] elements in the deque.
type unboundedSliceDeque[T any] struct {
size, left, right int
data []T
}
func (b *unboundedSliceDeque[T]) PushRight(elt T) bool {
// Invariant (2) says it's safe to place the element without resizing.
b.data[b.right] = elt
b.size++
b.right++
b.right %= len(b.data)
b.resize()
return true
}
func (b *unboundedSliceDeque[T]) PushLeft(elt T) bool {
// Invariant (1) says it's safe to place the element without resizing.
b.data[b.left] = elt
b.size++
b.left--
if b.left < 0 {
b.left = len(b.data) - 1 // Wrap around
}
b.resize()
return true
}
func (b *unboundedSliceDeque[T]) PopLeft() (T, bool) {
if b.size == 0 {
return utils.Zero[T](), false
}
idx := b.leftmostEltIdx()
elt := b.data[idx]
// Zero out to prevent memory leak.
b.data[idx] = utils.Zero[T]()
b.size--
b.left++
b.left %= len(b.data)
return elt, true
}
func (b *unboundedSliceDeque[T]) PeekLeft() (T, bool) {
if b.size == 0 {
return utils.Zero[T](), false
}
idx := b.leftmostEltIdx()
return b.data[idx], true
}
func (b *unboundedSliceDeque[T]) PopRight() (T, bool) {
if b.size == 0 {
return utils.Zero[T](), false
}
idx := b.rightmostEltIdx()
elt := b.data[idx]
// Zero out to prevent memory leak.
b.data[idx] = utils.Zero[T]()
b.size--
b.right--
if b.right < 0 {
b.right = len(b.data) - 1 // Wrap around
}
return elt, true
}
func (b *unboundedSliceDeque[T]) PeekRight() (T, bool) {
if b.size == 0 {
return utils.Zero[T](), false
}
idx := b.rightmostEltIdx()
return b.data[idx], true
}
func (b *unboundedSliceDeque[T]) Index(idx int) (T, bool) {
if idx < 0 || idx >= b.size {
return utils.Zero[T](), false
}
leftmostIdx := b.leftmostEltIdx()
idx = (leftmostIdx + idx) % len(b.data)
return b.data[idx], true
}
func (b *unboundedSliceDeque[T]) Len() int {
return b.size
}
func (b *unboundedSliceDeque[T]) List() []T {
if b.size == 0 {
return nil
}
list := make([]T, b.size)
leftmostIdx := b.leftmostEltIdx()
if numCopied := copy(list, b.data[leftmostIdx:]); numCopied < b.size {
// We copied all of the elements from the leftmost element index
// to the end of the underlying slice, but we still haven't copied
// all of the elements, so wrap around and copy the rest.
copy(list[numCopied:], b.data[:b.right])
}
return list
}
func (b *unboundedSliceDeque[T]) leftmostEltIdx() int {
if b.left == len(b.data)-1 { // Wrap around case
return 0
}
return b.left + 1 // Normal case
}
func (b *unboundedSliceDeque[T]) rightmostEltIdx() int {
if b.right == 0 {
return len(b.data) - 1 // Wrap around case
}
return b.right - 1 // Normal case
}
func (b *unboundedSliceDeque[T]) resize() {
if b.size != len(b.data) {
return
}
newData := make([]T, b.size*2)
leftmostIdx := b.leftmostEltIdx()
copy(newData, b.data[leftmostIdx:])
numCopied := len(b.data) - leftmostIdx
copy(newData[numCopied:], b.data[:b.right])
b.data = newData
b.left = len(b.data) - 1
b.right = b.size
}
-672
View File
@@ -1,672 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package buffer
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestUnboundedDeque_InitialCapGreaterThanMin(t *testing.T) {
require := require.New(t)
bIntf := NewUnboundedDeque[int](10)
require.IsType(&unboundedSliceDeque[int]{}, bIntf)
b := bIntf.(*unboundedSliceDeque[int])
require.Empty(b.List())
require.Zero(b.Len())
_, ok := b.Index(0)
require.False(ok)
b.PushLeft(1)
require.Equal(1, b.Len())
require.Equal([]int{1}, b.List())
got, ok := b.Index(0)
require.True(ok)
require.Equal(1, got)
_, ok = b.Index(1)
require.False(ok)
got, ok = b.PopLeft()
require.Zero(b.Len())
require.True(ok)
require.Equal(1, got)
_, ok = b.Index(0)
require.False(ok)
b.PushLeft(1)
require.Equal(1, b.Len())
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.PopRight()
require.Zero(b.Len())
require.True(ok)
require.Equal(1, got)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
b.PushRight(1)
require.Equal(1, b.Len())
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.PopRight()
require.Zero(b.Len())
require.True(ok)
require.Equal(1, got)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
b.PushRight(1)
require.Equal(1, b.Len())
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.PopLeft()
require.Zero(b.Len())
require.True(ok)
require.Equal(1, got)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
b.PushLeft(1)
require.Equal(1, b.Len())
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
b.PushLeft(2)
require.Equal(2, b.Len())
require.Equal([]int{2, 1}, b.List())
got, ok = b.PopLeft()
require.Equal(1, b.Len())
require.True(ok)
require.Equal(2, got)
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.PopLeft()
require.Zero(b.Len())
require.True(ok)
require.Equal(1, got)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
b.PushRight(1)
require.Equal(1, b.Len())
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
b.PushRight(2)
require.Equal(2, b.Len())
require.Equal([]int{1, 2}, b.List())
got, ok = b.PopRight()
require.Equal(1, b.Len())
require.True(ok)
require.Equal(2, got)
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.PopRight()
require.Zero(b.Len())
require.True(ok)
require.Equal(1, got)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
b.PushLeft(1)
require.Equal(1, b.Len())
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
b.PushLeft(2)
require.Equal(2, b.Len())
require.Equal([]int{2, 1}, b.List())
got, ok = b.PopRight()
require.Equal(1, b.Len())
require.True(ok)
require.Equal(1, got)
require.Equal([]int{2}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(2, got)
got, ok = b.PopLeft()
require.Zero(b.Len())
require.True(ok)
require.Equal(2, got)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
b.PushRight(1)
require.Equal(1, b.Len())
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
b.PushLeft(2)
require.Equal(2, b.Len())
require.Equal([]int{2, 1}, b.List())
got, ok = b.PopRight()
require.Equal(1, b.Len())
require.True(ok)
require.Equal(1, got)
require.Equal([]int{2}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(2, got)
got, ok = b.PopLeft()
require.Zero(b.Len())
require.True(ok)
require.Equal(2, got)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
b.PushLeft(1)
require.Equal(1, b.Len())
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
b.PushRight(2)
require.Equal(2, b.Len())
require.Equal([]int{1, 2}, b.List())
got, ok = b.PopLeft()
require.Equal(1, b.Len())
require.True(ok)
require.Equal(1, got)
require.Equal([]int{2}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(2, got)
got, ok = b.PopRight()
require.Zero(b.Len())
require.True(ok)
require.Equal(2, got)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
}
// Cases we test:
// 1. [left] moves to the left (no wrap around).
// 2. [left] moves to the right (no wrap around).
// 3. [left] wrapping around to the left side.
// 4. [left] wrapping around to the right side.
// 5. Resize.
func TestUnboundedSliceDequePushLeftPopLeft(t *testing.T) {
require := require.New(t)
// Starts empty.
bIntf := NewUnboundedDeque[int](2)
require.IsType(&unboundedSliceDeque[int]{}, bIntf)
b := bIntf.(*unboundedSliceDeque[int])
require.Zero(bIntf.Len())
require.Len(b.data, 2)
require.Zero(b.left)
require.Equal(1, b.right)
require.Empty(b.List())
// slice is [EMPTY]
_, ok := b.PopLeft()
require.False(ok)
_, ok = b.PeekLeft()
require.False(ok)
_, ok = b.PeekRight()
require.False(ok)
b.PushLeft(1) // slice is [1,EMPTY]
require.Equal(1, b.Len())
require.Len(b.data, 2)
require.Equal(1, b.left)
require.Equal(1, b.right)
require.Equal([]int{1}, b.List())
got, ok := b.PeekLeft()
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(1, got)
// This causes a resize
b.PushLeft(2) // slice is [2,1,EMPTY,EMPTY]
require.Equal(2, b.Len())
require.Len(b.data, 4)
require.Equal(3, b.left)
require.Equal(2, b.right)
require.Equal([]int{2, 1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(2, got)
got, ok = b.Index(1)
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(2, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(1, got)
// Tests left moving left with no wrap around.
b.PushLeft(3) // slice is [2,1,EMPTY,3]
require.Equal(3, b.Len())
require.Len(b.data, 4)
require.Equal(2, b.left)
require.Equal(2, b.right)
require.Equal([]int{3, 2, 1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(3, got)
got, ok = b.Index(1)
require.True(ok)
require.Equal(2, got)
got, ok = b.Index(2)
require.True(ok)
require.Equal(1, got)
_, ok = b.Index(3)
require.False(ok)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(3, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(1, got)
// Tests left moving right with no wrap around.
got, ok = b.PopLeft() // slice is [2,1,EMPTY,EMPTY]
require.True(ok)
require.Equal(3, got)
require.Equal(2, b.Len())
require.Len(b.data, 4)
require.Equal(3, b.left)
require.Equal(2, b.right)
require.Equal([]int{2, 1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(2, got)
got, ok = b.Index(1)
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(2, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(1, got)
// Tests left wrapping around to the left side.
got, ok = b.PopLeft() // slice is [EMPTY,1,EMPTY,EMPTY]
require.True(ok)
require.Equal(2, got)
require.Equal(1, b.Len())
require.Len(b.data, 4)
require.Zero(b.left)
require.Equal(2, b.right)
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(1, got)
// Test left wrapping around to the right side.
b.PushLeft(2) // slice is [2,1,EMPTY,EMPTY]
require.Equal(2, b.Len())
require.Len(b.data, 4)
require.Equal(3, b.left)
require.Equal(2, b.right)
require.Equal([]int{2, 1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(2, got)
got, ok = b.Index(1)
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(2, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(1, got)
got, ok = b.PopLeft() // slice is [EMPTY,1,EMPTY,EMPTY]
require.True(ok)
require.Equal(2, got)
require.Equal(1, b.Len())
require.Len(b.data, 4)
require.Zero(b.left)
require.Equal(2, b.right)
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.PopLeft() // slice is [EMPTY,EMPTY,EMPTY,EMPTY]
require.True(ok)
require.Equal(1, got)
require.Zero(b.Len())
require.Len(b.data, 4)
require.Equal(1, b.left)
require.Equal(2, b.right)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
_, ok = b.PopLeft()
require.False(ok)
_, ok = b.PeekLeft()
require.False(ok)
_, ok = b.PeekRight()
require.False(ok)
}
func TestUnboundedSliceDequePushRightPopRight(t *testing.T) {
require := require.New(t)
// Starts empty.
bIntf := NewUnboundedDeque[int](2)
require.IsType(&unboundedSliceDeque[int]{}, bIntf)
b := bIntf.(*unboundedSliceDeque[int])
require.Zero(bIntf.Len())
require.Len(b.data, 2)
require.Zero(b.left)
require.Equal(1, b.right)
require.Empty(b.List())
// slice is [EMPTY]
_, ok := b.PopRight()
require.False(ok)
_, ok = b.PeekLeft()
require.False(ok)
_, ok = b.PeekRight()
require.False(ok)
b.PushRight(1) // slice is [1,EMPTY]
require.Equal(1, b.Len())
require.Len(b.data, 2)
require.Zero(b.left)
require.Zero(b.right)
require.Equal([]int{1}, b.List())
got, ok := b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(1, got)
// This causes a resize
b.PushRight(2) // slice is [1,2,EMPTY,EMPTY]
require.Equal(2, b.Len())
require.Len(b.data, 4)
require.Equal(3, b.left)
require.Equal(2, b.right)
require.Equal([]int{1, 2}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.Index(1)
require.True(ok)
require.Equal(2, got)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(2, got)
// Tests right moving right with no wrap around
b.PushRight(3) // slice is [1,2,3,EMPTY]
require.Equal(3, b.Len())
require.Len(b.data, 4)
require.Equal(3, b.left)
require.Equal(3, b.right)
require.Equal([]int{1, 2, 3}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.Index(1)
require.True(ok)
require.Equal(2, got)
got, ok = b.Index(2)
require.True(ok)
require.Equal(3, got)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(3, got)
// Tests right moving left with no wrap around
got, ok = b.PopRight() // slice is [1,2,EMPTY,EMPTY]
require.True(ok)
require.Equal(3, got)
require.Equal(2, b.Len())
require.Len(b.data, 4)
require.Equal(3, b.left)
require.Equal(2, b.right)
require.Equal([]int{1, 2}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.Index(1)
require.True(ok)
require.Equal(2, got)
_, ok = b.Index(2)
require.False(ok)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(2, got)
got, ok = b.PopRight() // slice is [1,EMPTY,EMPTY,EMPTY]
require.True(ok)
require.Equal(2, got)
require.Equal(1, b.Len())
require.Len(b.data, 4)
require.Equal(3, b.left)
require.Equal(1, b.right)
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
_, ok = b.Index(1)
require.False(ok)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(1, got)
got, ok = b.PopRight() // slice is [EMPTY,EMPTY,EMPTY,EMPTY]
require.True(ok)
require.Equal(1, got)
require.Zero(b.Len())
require.Len(b.data, 4)
require.Equal(3, b.left)
require.Zero(b.right)
require.Empty(b.List())
require.Zero(b.Len())
_, ok = b.Index(0)
require.False(ok)
_, ok = b.PeekLeft()
require.False(ok)
_, ok = b.PeekRight()
require.False(ok)
_, ok = b.PopRight()
require.False(ok)
b.PushLeft(1) // slice is [EMPTY,EMPTY,EMPTY,1]
require.Equal(1, b.Len())
require.Len(b.data, 4)
require.Equal(2, b.left)
require.Zero(b.right)
require.Equal([]int{1}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(1, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(1, got)
// Test right wrapping around to the right
got, ok = b.PopRight() // slice is [EMPTY,EMPTY,EMPTY,EMPTY]
require.True(ok)
require.Equal(1, got)
require.Zero(b.Len())
require.Len(b.data, 4)
require.Equal(2, b.left)
require.Equal(3, b.right)
require.Empty(b.List())
require.Zero(b.Len())
_, ok = b.Index(0)
require.False(ok)
_, ok = b.PeekLeft()
require.False(ok)
_, ok = b.PeekRight()
require.False(ok)
// Tests right wrapping around to the left
b.PushRight(2) // slice is [EMPTY,EMPTY,EMPTY,2]
require.Equal(1, b.Len())
require.Len(b.data, 4)
require.Equal(2, b.left)
require.Zero(b.right)
require.Equal([]int{2}, b.List())
got, ok = b.Index(0)
require.True(ok)
require.Equal(2, got)
got, ok = b.PeekLeft()
require.True(ok)
require.Equal(2, got)
got, ok = b.PeekRight()
require.True(ok)
require.Equal(2, got)
got, ok = b.PopRight() // slice is [EMPTY,EMPTY,EMPTY,EMPTY]
require.True(ok)
require.Equal(2, got)
require.Empty(b.List())
_, ok = b.Index(0)
require.False(ok)
_, ok = b.PeekLeft()
require.False(ok)
_, ok = b.PeekRight()
require.False(ok)
_, ok = b.PopRight()
require.False(ok)
}
func FuzzUnboundedSliceDeque(f *testing.F) {
f.Fuzz(
func(t *testing.T, initSize uint, input []byte) {
require := require.New(t)
b := NewUnboundedDeque[byte](int(initSize))
for i, n := range input {
b.PushRight(n)
gotIndex, ok := b.Index(i)
require.True(ok)
require.Equal(n, gotIndex)
}
list := b.List()
require.Len(list, len(input))
for i, n := range input {
require.Equal(n, list[i])
}
for i := 0; i < len(input); i++ {
_, _ = b.PopLeft()
list = b.List()
if i == len(input)-1 {
require.Empty(list)
_, ok := b.Index(0)
require.False(ok)
} else {
require.Equal(input[i+1:], list)
got, ok := b.Index(0)
require.True(ok)
require.Equal(input[i+1], got)
}
}
},
)
}
-83
View File
@@ -1,83 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package utils
import (
"crypto/rand"
"math/bits"
"sync"
)
// RandomBytes returns a slice of n random bytes
// Intended for use in testing
func RandomBytes(n int) []byte {
b := make([]byte, n)
_, _ = rand.Read(b)
return b
}
// Constant taken from the "math" package
const intSize = 32 << (^uint(0) >> 63) // 32 or 64
// BytesPool tracks buckets of available buffers to be allocated. Each bucket
// allocates buffers of the following length:
//
// 0
// 1
// 3
// 7
// 15
// 31
// 63
// 127
// ...
// MaxInt
//
// In order to allocate a buffer of length 19 (for example), we calculate the
// number of bits required to represent 19 (5). And therefore allocate a slice
// from bucket 5, which has length 31. This is the bucket which produces the
// smallest slices that are at least length 19.
//
// When replacing a buffer of length 19, we calculate the number of bits
// required to represent 20 (5). And therefore place the slice into bucket 4,
// which has length 15. This is the bucket which produces the largest slices
// that a length 19 slice can be used for.
type BytesPool [intSize]sync.Pool
func NewBytesPool() *BytesPool {
var p BytesPool
for i := range p {
// uint is used here to avoid overflowing int during the shift
size := uint(1)<<i - 1
p[i] = sync.Pool{
New: func() interface{} {
// Sync pool needs to return pointer-like values to avoid memory
// allocations.
b := make([]byte, size)
return &b
},
}
}
return &p
}
// Get returns a non-nil pointer to a slice with the requested length.
//
// It is not guaranteed for the returned bytes to have been zeroed.
func (p *BytesPool) Get(length int) *[]byte {
index := bits.Len(uint(length)) // Round up
bytes := p[index].Get().(*[]byte)
*bytes = (*bytes)[:length] // Set the length to be the expected value
return bytes
}
// Put takes ownership of a non-nil pointer to a slice of bytes.
//
// Note: this function takes ownership of the underlying array. So, the length
// of the provided slice is ignored and only its capacity is used.
func (p *BytesPool) Put(bytes *[]byte) {
size := cap(*bytes)
index := bits.Len(uint(size)+1) - 1 // Round down
p[index].Put(bytes)
}
-91
View File
@@ -1,91 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package utils
import (
"math"
"math/rand"
"strconv"
"testing"
"github.com/stretchr/testify/require"
)
// Verify that [intSize] is correctly set based on the word size. This test uses
// [math.MaxInt] to detect the word size.
func TestIntSize(t *testing.T) {
require := require.New(t)
require.Contains([]int{32, 64}, intSize)
if intSize == 32 {
require.Equal(math.MaxInt32, math.MaxInt)
} else {
require.Equal(math.MaxInt64, math.MaxInt)
}
}
func TestBytesPool(t *testing.T) {
require := require.New(t)
p := NewBytesPool()
for i := 0; i < 128; i++ {
bytes := p.Get(i)
require.NotNil(bytes)
require.Len(*bytes, i)
p.Put(bytes)
}
}
func BenchmarkBytesPool_Constant(b *testing.B) {
sizes := []int{
0,
8,
16,
32,
64,
256,
2048,
}
for _, size := range sizes {
b.Run(strconv.Itoa(size), func(b *testing.B) {
p := NewBytesPool()
for i := 0; i < b.N; i++ {
p.Put(p.Get(size))
}
})
}
}
func BenchmarkBytesPool_Descending(b *testing.B) {
p := NewBytesPool()
for i := 0; i < b.N; i++ {
for size := 100_000; size > 0; size-- {
p.Put(p.Get(size))
}
}
}
func BenchmarkBytesPool_Ascending(b *testing.B) {
p := NewBytesPool()
for i := 0; i < b.N; i++ {
for size := 0; size < 100_000; size++ {
p.Put(p.Get(size))
}
}
}
func BenchmarkBytesPool_Random(b *testing.B) {
p := NewBytesPool()
sizes := make([]int, 1_000)
for i := range sizes {
sizes[i] = rand.Intn(100_000) //#nosec G404
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, size := range sizes {
p.Put(p.Get(size))
}
}
}
-56
View File
@@ -1,56 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cb58
import (
"bytes"
"errors"
"fmt"
"math"
"github.com/mr-tron/base58/base58"
"github.com/luxfi/node/utils/hashing"
)
const checksumLen = 4
var (
ErrBase58Decoding = errors.New("base58 decoding error")
ErrMissingChecksum = errors.New("input string is smaller than the checksum size")
ErrBadChecksum = errors.New("invalid input checksum")
errEncodingOverFlow = errors.New("encoding overflow")
)
// Encode [bytes] to a string using cb58 format.
// [bytes] may be nil, in which case it will be treated the same as an empty
// slice.
func Encode(bytes []byte) (string, error) {
bytesLen := len(bytes)
if bytesLen > math.MaxInt32-checksumLen {
return "", errEncodingOverFlow
}
checked := make([]byte, bytesLen+checksumLen)
copy(checked, bytes)
copy(checked[len(bytes):], hashing.Checksum(bytes, checksumLen))
return base58.Encode(checked), nil
}
// Decode [str] to bytes from cb58.
func Decode(str string) ([]byte, error) {
decodedBytes, err := base58.Decode(str)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrBase58Decoding, err)
}
if len(decodedBytes) < checksumLen {
return nil, ErrMissingChecksum
}
// Verify the checksum
rawBytes := decodedBytes[:len(decodedBytes)-checksumLen]
checksum := decodedBytes[len(decodedBytes)-checksumLen:]
if !bytes.Equal(checksum, hashing.Checksum(rawBytes, checksumLen)) {
return nil, ErrBadChecksum
}
return rawBytes, nil
}
-74
View File
@@ -1,74 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package cb58
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
)
// Test encoding bytes to a string and decoding back to bytes
func TestEncodeDecode(t *testing.T) {
require := require.New(t)
type test struct {
bytes []byte
str string
}
id := [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
tests := []test{
{
nil,
"45PJLL",
},
{
[]byte{},
"45PJLL",
},
{
[]byte{0},
"1c7hwa",
},
{
[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255},
"1NVSVezva3bAtJesnUj",
},
{
id[:],
"SkB92YpWm4Q2ijQHH34cqbKkCZWszsiQgHVjtNeFF2HdvDQU",
},
}
for _, test := range tests {
// Encode the bytes
strResult, err := Encode(test.bytes)
require.NoError(err)
// Make sure the string repr. is what we expected
require.Equal(test.str, strResult)
// Decode the string
bytesResult, err := Decode(strResult)
require.NoError(err)
// Make sure we got the same bytes back
require.True(bytes.Equal(test.bytes, bytesResult))
}
}
func FuzzEncodeDecode(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
require := require.New(t)
// Encode bytes to string
dataStr, err := Encode(data)
require.NoError(err)
// Decode string to bytes
gotData, err := Decode(dataStr)
require.NoError(err)
require.Equal(data, gotData)
})
}
-12
View File
@@ -1,12 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package compression
// Compressor compress and decompresses messages.
// Decompress is the inverse of Compress.
// Decompress(Compress(msg)) == msg.
type Compressor interface {
Compress([]byte) ([]byte, error)
Decompress([]byte) ([]byte, error)
}
-245
View File
@@ -1,245 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package compression
import (
"fmt"
"math"
"runtime"
"testing"
"github.com/stretchr/testify/require"
_ "embed"
"github.com/luxfi/node/utils"
"github.com/luxfi/node/utils/units"
)
const maxMessageSize = 2 * units.MiB // Max message size. Can't import due to cycle.
var (
newCompressorFuncs = map[Type]func(maxSize int64) (Compressor, error){
TypeNone: func(int64) (Compressor, error) { //nolint:unparam // an error is needed to be returned to compile
return NewNoCompressor(), nil
},
TypeZstd: NewZstdCompressor,
}
//go:embed zstd_zip_bomb.bin
zstdZipBomb []byte
zipBombs = map[Type][]byte{
TypeZstd: zstdZipBomb,
}
)
func TestDecompressZipBombs(t *testing.T) {
for compressionType, zipBomb := range zipBombs {
// Make sure that the hardcoded zip bomb would be a valid message.
require.Less(t, len(zipBomb), maxMessageSize)
newCompressorFunc := newCompressorFuncs[compressionType]
t.Run(compressionType.String(), func(t *testing.T) {
require := require.New(t)
compressor, err := newCompressorFunc(maxMessageSize)
require.NoError(err)
var (
beforeDecompressionStats runtime.MemStats
afterDecompressionStats runtime.MemStats
)
runtime.ReadMemStats(&beforeDecompressionStats)
_, err = compressor.Decompress(zipBomb)
runtime.ReadMemStats(&afterDecompressionStats)
require.ErrorIs(err, ErrDecompressedMsgTooLarge)
// Make sure that we didn't allocate significantly more memory than
// the max message size.
bytesAllocatedDuringDecompression := afterDecompressionStats.TotalAlloc - beforeDecompressionStats.TotalAlloc
require.Less(bytesAllocatedDuringDecompression, uint64(10*maxMessageSize))
})
}
}
func TestCompressDecompress(t *testing.T) {
for compressionType, newCompressorFunc := range newCompressorFuncs {
t.Run(compressionType.String(), func(t *testing.T) {
require := require.New(t)
data := utils.RandomBytes(4096)
data2 := utils.RandomBytes(4096)
compressor, err := newCompressorFunc(maxMessageSize)
require.NoError(err)
dataCompressed, err := compressor.Compress(data)
require.NoError(err)
data2Compressed, err := compressor.Compress(data2)
require.NoError(err)
dataDecompressed, err := compressor.Decompress(dataCompressed)
require.NoError(err)
require.Equal(data, dataDecompressed)
data2Decompressed, err := compressor.Decompress(data2Compressed)
require.NoError(err)
require.Equal(data2, data2Decompressed)
dataDecompressed, err = compressor.Decompress(dataCompressed)
require.NoError(err)
require.Equal(data, dataDecompressed)
maxMessage := utils.RandomBytes(maxMessageSize)
maxMessageCompressed, err := compressor.Compress(maxMessage)
require.NoError(err)
maxMessageDecompressed, err := compressor.Decompress(maxMessageCompressed)
require.NoError(err)
require.Equal(maxMessage, maxMessageDecompressed)
})
}
}
func TestSizeLimiting(t *testing.T) {
for compressionType, compressorFunc := range newCompressorFuncs {
if compressionType == TypeNone {
continue
}
t.Run(compressionType.String(), func(t *testing.T) {
require := require.New(t)
compressor, err := compressorFunc(maxMessageSize)
require.NoError(err)
data := make([]byte, maxMessageSize+1)
_, err = compressor.Compress(data) // should be too large
require.ErrorIs(err, ErrMsgTooLarge)
compressor2, err := compressorFunc(2 * maxMessageSize)
require.NoError(err)
dataCompressed, err := compressor2.Compress(data)
require.NoError(err)
_, err = compressor.Decompress(dataCompressed) // should be too large
require.ErrorIs(err, ErrDecompressedMsgTooLarge)
})
}
}
// Attempts to create a compressor with math.MaxInt64
// which leads to undefined decompress behavior due to integer overflow
// in limit reader creation.
func TestNewCompressorWithInvalidLimit(t *testing.T) {
for compressionType, compressorFunc := range newCompressorFuncs {
if compressionType == TypeNone {
continue
}
t.Run(compressionType.String(), func(t *testing.T) {
_, err := compressorFunc(math.MaxInt64)
require.ErrorIs(t, err, ErrInvalidMaxSizeCompressor)
})
}
}
func FuzzZstdCompressor(f *testing.F) {
fuzzHelper(f, TypeZstd)
}
func fuzzHelper(f *testing.F, compressionType Type) {
var (
compressor Compressor
err error
)
switch compressionType {
case TypeZstd:
compressor, err = NewZstdCompressor(maxMessageSize)
require.NoError(f, err)
default:
require.FailNow(f, "Unknown compression type")
}
f.Fuzz(func(t *testing.T, data []byte) {
require := require.New(t)
if len(data) > maxMessageSize {
_, err := compressor.Compress(data)
require.ErrorIs(err, ErrMsgTooLarge)
}
compressed, err := compressor.Compress(data)
require.NoError(err)
decompressed, err := compressor.Decompress(compressed)
require.NoError(err)
require.Equal(data, decompressed)
})
}
func BenchmarkCompress(b *testing.B) {
sizes := []int{
0,
256,
units.KiB,
units.MiB,
maxMessageSize,
}
for compressionType, newCompressorFunc := range newCompressorFuncs {
if compressionType == TypeNone {
continue
}
for _, size := range sizes {
b.Run(fmt.Sprintf("%s_%d", compressionType, size), func(b *testing.B) {
require := require.New(b)
bytes := utils.RandomBytes(size)
compressor, err := newCompressorFunc(maxMessageSize)
require.NoError(err)
for n := 0; n < b.N; n++ {
_, err := compressor.Compress(bytes)
require.NoError(err)
}
})
}
}
}
func BenchmarkDecompress(b *testing.B) {
sizes := []int{
0,
256,
units.KiB,
units.MiB,
maxMessageSize,
}
for compressionType, newCompressorFunc := range newCompressorFuncs {
if compressionType == TypeNone {
continue
}
for _, size := range sizes {
b.Run(fmt.Sprintf("%s_%d", compressionType, size), func(b *testing.B) {
require := require.New(b)
bytes := utils.RandomBytes(size)
compressor, err := newCompressorFunc(maxMessageSize)
require.NoError(err)
compressedBytes, err := compressor.Compress(bytes)
require.NoError(err)
for n := 0; n < b.N; n++ {
_, err := compressor.Decompress(compressedBytes)
require.NoError(err)
}
})
}
}
}
-12
View File
@@ -1,12 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
package compression
import "errors"
var (
ErrInvalidMaxSizeCompressor = errors.New("invalid compressor max size")
ErrDecompressedMsgTooLarge = errors.New("decompressed msg too large")
ErrMsgTooLarge = errors.New("msg too large to be compressed")
)
-83
View File
@@ -1,83 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
package compression
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"math"
"sync"
)
var _ Compressor = (*gzipCompressor)(nil)
type gzipCompressor struct {
maxSize int64
gzipWriterPool sync.Pool
}
// Compress [msg] and returns the compressed bytes.
func (g *gzipCompressor) Compress(msg []byte) ([]byte, error) {
if int64(len(msg)) > g.maxSize {
return nil, fmt.Errorf("%w: (%d) > (%d)", ErrMsgTooLarge, len(msg), g.maxSize)
}
var writeBuffer bytes.Buffer
gzipWriter := g.gzipWriterPool.Get().(*gzip.Writer)
gzipWriter.Reset(&writeBuffer)
defer g.gzipWriterPool.Put(gzipWriter)
if _, err := gzipWriter.Write(msg); err != nil {
return nil, err
}
if err := gzipWriter.Close(); err != nil {
return nil, err
}
return writeBuffer.Bytes(), nil
}
// Decompress decompresses [msg].
func (g *gzipCompressor) Decompress(msg []byte) ([]byte, error) {
bytesReader := bytes.NewReader(msg)
gzipReader, err := gzip.NewReader(bytesReader)
if err != nil {
return nil, err
}
// We allow [io.LimitReader] to read up to [g.maxSize + 1] bytes, so that if
// the decompressed payload is greater than the maximum size, this function
// will return the appropriate error instead of an incomplete byte slice.
limitedReader := io.LimitReader(gzipReader, g.maxSize+1)
decompressed, err := io.ReadAll(limitedReader)
if err != nil {
return nil, err
}
if int64(len(decompressed)) > g.maxSize {
return nil, fmt.Errorf("%w: (%d) > (%d)", ErrDecompressedMsgTooLarge, len(decompressed), g.maxSize)
}
return decompressed, gzipReader.Close()
}
// NewGzipCompressor returns a new gzip Compressor that compresses
func NewGzipCompressor(maxSize int64) (Compressor, error) {
if maxSize == math.MaxInt64 {
// "Decompress" creates "io.LimitReader" with max size + 1:
// if the max size + 1 overflows, "io.LimitReader" reads nothing
// returning 0 byte for the decompress call
// require max size <math.MaxInt64 to prevent int64 overflows
return nil, ErrInvalidMaxSizeCompressor
}
return &gzipCompressor{
maxSize: maxSize,
gzipWriterPool: sync.Pool{
New: func() interface{} {
return gzip.NewWriter(nil)
},
},
}, nil
}
Binary file not shown.
-20
View File
@@ -1,20 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package compression
var _ Compressor = (*noCompressor)(nil)
type noCompressor struct{}
func (*noCompressor) Compress(msg []byte) ([]byte, error) {
return msg, nil
}
func (*noCompressor) Decompress(msg []byte) ([]byte, error) {
return msg, nil
}
func NewNoCompressor() Compressor {
return &noCompressor{}
}
-24
View File
@@ -1,24 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package compression
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestNoCompressor(t *testing.T) {
require := require.New(t)
data := []byte{1, 2, 3}
compressor := NewNoCompressor()
compressedBytes, err := compressor.Compress(data)
require.NoError(err)
require.Equal(data, compressedBytes)
decompressedBytes, err := compressor.Decompress(compressedBytes)
require.NoError(err)
require.Equal(data, decompressedBytes)
}
-54
View File
@@ -1,54 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package compression
import (
"errors"
"strings"
)
var errUnknownCompressionType = errors.New("unknown compression type")
type Type byte
const (
TypeNone Type = iota + 1
TypeZstd
)
func (t Type) String() string {
switch t {
case TypeNone:
return "none"
case TypeZstd:
return "zstd"
default:
return "unknown"
}
}
func TypeFromString(s string) (Type, error) {
switch s {
case TypeNone.String():
return TypeNone, nil
case TypeZstd.String():
return TypeZstd, nil
default:
return TypeNone, errUnknownCompressionType
}
}
func (t Type) MarshalJSON() ([]byte, error) {
var b strings.Builder
if _, err := b.WriteString(`"`); err != nil {
return nil, err
}
if _, err := b.WriteString(t.String()); err != nil {
return nil, err
}
if _, err := b.WriteString(`"`); err != nil {
return nil, err
}
return []byte(b.String()), nil
}
-56
View File
@@ -1,56 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package compression
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestTypeString(t *testing.T) {
require := require.New(t)
for _, compressionType := range []Type{TypeNone, TypeZstd} {
s := compressionType.String()
parsedType, err := TypeFromString(s)
require.NoError(err)
require.Equal(compressionType, parsedType)
}
_, err := TypeFromString("unknown")
require.ErrorIs(err, errUnknownCompressionType)
}
func TestTypeMarshalJSON(t *testing.T) {
type test struct {
Type Type
expected string
}
tests := []test{
{
Type: TypeNone,
expected: `"none"`,
},
{
Type: TypeZstd,
expected: `"zstd"`,
},
{
Type: Type(0),
expected: `"unknown"`,
},
}
for _, tt := range tests {
t.Run(tt.Type.String(), func(t *testing.T) {
require := require.New(t)
b, err := tt.Type.MarshalJSON()
require.NoError(err)
require.Equal(tt.expected, string(b))
})
}
}
-68
View File
@@ -1,68 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package compression
import (
"fmt"
"math"
"github.com/klauspost/compress/zstd"
)
var _ Compressor = (*zstdCompressor)(nil)
func NewZstdCompressor(maxSize int64) (Compressor, error) {
return NewZstdCompressorWithLevel(maxSize, zstd.SpeedDefault)
}
func NewZstdCompressorWithLevel(maxSize int64, level zstd.EncoderLevel) (Compressor, error) {
if maxSize == math.MaxInt64 {
// "Decompress" creates "io.LimitReader" with max size + 1:
// if the max size + 1 overflows, "io.LimitReader" reads nothing
// returning 0 byte for the decompress call
// require max size < math.MaxInt64 to prevent int64 overflows
return nil, ErrInvalidMaxSizeCompressor
}
decoder, err := zstd.NewReader(nil)
if err != nil {
return nil, err
}
return &zstdCompressor{
maxSize: maxSize,
level: level,
decoder: decoder,
}, nil
}
type zstdCompressor struct {
maxSize int64
level zstd.EncoderLevel
decoder *zstd.Decoder
}
func (z *zstdCompressor) Compress(msg []byte) ([]byte, error) {
if int64(len(msg)) > z.maxSize {
return nil, fmt.Errorf("%w: (%d) > (%d)", ErrMsgTooLarge, len(msg), z.maxSize)
}
encoder, err := zstd.NewWriter(nil, zstd.WithEncoderLevel(z.level))
if err != nil {
return nil, err
}
return encoder.EncodeAll(msg, nil), nil
}
func (z *zstdCompressor) Decompress(msg []byte) ([]byte, error) {
decompressed, err := z.decoder.DecodeAll(msg, nil)
if err != nil {
// If the decoder returns an error about size limit, wrap it with our error
if err.Error() == "decompressed size exceeds configured limit" {
return nil, fmt.Errorf("%w: decompression stopped due to size limit", ErrDecompressedMsgTooLarge)
}
return nil, err
}
if int64(len(decompressed)) > z.maxSize {
return nil, fmt.Errorf("%w: (%d) > (%d)", ErrDecompressedMsgTooLarge, len(decompressed), z.maxSize)
}
return decompressed, nil
}
Binary file not shown.
-10
View File
@@ -1,10 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package constants
// ChainAliasPrefix denotes a prefix for an alias that belongs to a blockchain ID.
const ChainAliasPrefix string = "bc"
// VMAliasPrefix denotes a prefix for an alias that belongs to a VM ID.
const VMAliasPrefix string = "vm"
-13
View File
@@ -1,13 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package constants
// Const variables to be exported
const (
// PlatformName exports the name of the platform
PlatformName = "lux"
// AppName exports the name of the lux application
AppName = "node"
)
-52
View File
@@ -1,52 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package constants
import "github.com/luxfi/math/set"
var (
// ActivatedACPs is the set of ACPs that are activated.
//
// See: https://github.com/orgs/luxfi/projects/1
ActivatedACPs = set.Of[uint32](
// Durango:
23, // https://github.com/luxfi/ACPs/blob/main/ACPs/23-p-chain-native-transfers/README.md
24, // https://github.com/luxfi/ACPs/blob/main/ACPs/24-shanghai-eips/README.md
25, // https://github.com/luxfi/ACPs/blob/main/ACPs/25-vm-application-errors/README.md
30, // https://github.com/luxfi/ACPs/blob/main/ACPs/30-lux-warp-x-evm/README.md
31, // https://github.com/luxfi/ACPs/blob/main/ACPs/31-enable-subnet-ownership-transfer/README.md
41, // https://github.com/luxfi/ACPs/blob/main/ACPs/41-remove-pending-stakers/README.md
62, // https://github.com/luxfi/ACPs/blob/main/ACPs/62-disable-addvalidatortx-and-adddelegatortx/README.md
// Etna:
77, // https://github.com/luxfi/ACPs/blob/main/ACPs/77-reinventing-subnets/README.md
103, // https://github.com/luxfi/ACPs/blob/main/ACPs/103-dynamic-fees/README.md
118, // https://github.com/luxfi/ACPs/blob/main/ACPs/118-warp-signature-request/README.md
125, // https://github.com/luxfi/ACPs/blob/main/ACPs/125-basefee-reduction/README.md
131, // https://github.com/luxfi/ACPs/blob/main/ACPs/131-cancun-eips/README.md
151, // https://github.com/luxfi/ACPs/blob/main/ACPs/151-use-current-block-pchain-height-as-context/README.md
)
// CurrentACPs is the set of ACPs that are currently, at the time of
// release, marked as implementable and not activated.
//
// See: https://github.com/orgs/luxfi/projects/1
CurrentACPs = set.Of[uint32](
176, // https://github.com/luxfi/ACPs/blob/main/ACPs/176-dynamic-evm-gas-limit-and-price-discovery-updates/README.md
)
// ScheduledACPs are the ACPs included into the next upgrade.
ScheduledACPs = set.Of[uint32](
176, // https://github.com/luxfi/ACPs/blob/main/ACPs/176-dynamic-evm-gas-limit-and-price-discovery-updates/README.md
)
// CurrentLPs is the set of supported Lux Protocols
CurrentLPs = set.Of[uint32](176)
// ScheduledLPs are the LPs included in the next upgrade
ScheduledLPs = set.Of[uint32](176)
// ActivatedLPs is the set of activated LPs
ActivatedLPs = set.Of[uint32](176)
)
-8
View File
@@ -1,8 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package constants
// PointerOverhead is used to approximate the memory footprint from allocating a
// pointer.
const PointerOverhead = 8
-122
View File
@@ -1,122 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package constants
import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
)
// Const variables to be exported
const (
LocalID uint32 = 31337
MainnetID uint32 = 1 // Use 1 for Lux compatibility
TestnetID uint32 = 5 // Use 5 for Lux compatibility
UnitTestID uint32 = 369
// Lux-specific network IDs
LuxMainnetID uint32 = 96369 // Lux mainnet
LuxTestnetID uint32 = 96370 // Lux testnet
QChainMainnetID uint32 = 96380 // Q-Chain mainnet
QChainTestnetID uint32 = 96381 // Q-Chain testnet
LocalName = "local"
MainnetName = "mainnet"
TestnetName = "testnet"
UnitTestName = "testing"
FallbackHRP = "custom"
LocalHRP = "local"
MainnetHRP = "lux"
TestnetHRP = "test"
UnitTestHRP = "testing"
)
// Variables to be exported
var (
PrimaryNetworkID = ids.Empty
PlatformChainID = ids.Empty
// Q-Chain specific IDs
QChainID = ids.ID{'q', 'c', 'h', 'a', 'i', 'n'}
NetworkIDToNetworkName = map[uint32]string{
LocalID: LocalName,
MainnetID: MainnetName,
TestnetID: TestnetName,
UnitTestID: UnitTestName,
LuxMainnetID: MainnetName,
LuxTestnetID: TestnetName,
QChainMainnetID: "qchain-mainnet",
QChainTestnetID: "qchain-testnet",
}
NetworkNameToNetworkID = map[string]uint32{
LocalName: LocalID,
MainnetName: MainnetID,
TestnetName: TestnetID,
UnitTestName: UnitTestID,
}
NetworkIDToHRP = map[uint32]string{
LocalID: LocalHRP,
MainnetID: MainnetHRP,
TestnetID: TestnetHRP,
UnitTestID: UnitTestHRP,
LuxMainnetID: MainnetHRP,
LuxTestnetID: TestnetHRP,
QChainMainnetID: "qchain",
QChainTestnetID: "qtest",
}
NetworkHRPToNetworkID = map[string]uint32{
LocalHRP: LocalID,
MainnetHRP: MainnetID,
TestnetHRP: TestnetID,
UnitTestHRP: UnitTestID,
}
ProductionNetworkIDs = set.Of(MainnetID, TestnetID)
ValidNetworkPrefix = "network-"
ErrParseNetworkName = errors.New("failed to parse network name")
)
// GetHRP returns the Human-Readable-Part of bech32 addresses for a networkID
func GetHRP(networkID uint32) string {
if hrp, ok := NetworkIDToHRP[networkID]; ok {
return hrp
}
return FallbackHRP
}
// NetworkName returns a human readable name for the network with
// ID [networkID]
func NetworkName(networkID uint32) string {
if name, exists := NetworkIDToNetworkName[networkID]; exists {
return name
}
return fmt.Sprintf("network-%d", networkID)
}
// NetworkID returns the ID of the network with name [networkName]
func NetworkID(networkName string) (uint32, error) {
networkName = strings.ToLower(networkName)
if id, exists := NetworkNameToNetworkID[networkName]; exists {
return id, nil
}
idStr := networkName
if strings.HasPrefix(networkName, ValidNetworkPrefix) {
idStr = networkName[len(ValidNetworkPrefix):]
}
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
return 0, fmt.Errorf("%w: %q", ErrParseNetworkName, networkName)
}
return uint32(id), nil
}
-134
View File
@@ -1,134 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package constants
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGetHRP(t *testing.T) {
tests := []struct {
id uint32
hrp string
}{
{
id: MainnetID,
hrp: MainnetHRP,
},
{
id: TestnetID,
hrp: TestnetHRP,
},
{
id: TestnetID,
hrp: TestnetHRP,
},
{
id: LocalID,
hrp: LocalHRP,
},
{
id: 4294967295,
hrp: FallbackHRP,
},
}
for _, test := range tests {
t.Run(test.hrp, func(t *testing.T) {
require.Equal(t, test.hrp, GetHRP(test.id))
})
}
}
func TestNetworkName(t *testing.T) {
tests := []struct {
id uint32
name string
}{
{
id: MainnetID,
name: MainnetName,
},
{
id: TestnetID,
name: TestnetName,
},
{
id: TestnetID,
name: TestnetName,
},
{
id: LocalID,
name: LocalName,
},
{
id: 4294967295,
name: "network-4294967295",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.name, NetworkName(test.id))
})
}
}
func TestNetworkID(t *testing.T) {
tests := []struct {
name string
id uint32
expectedErr error
}{
{
name: MainnetName,
id: MainnetID,
},
{
name: "MaInNeT",
id: MainnetID,
},
{
name: TestnetName,
id: TestnetID,
},
{
name: TestnetName,
id: TestnetID,
},
{
name: LocalName,
id: LocalID,
},
{
name: "network-4294967295",
id: 4294967295,
},
{
name: "4294967295",
id: 4294967295,
},
{
name: "networ-4294967295",
expectedErr: ErrParseNetworkName,
},
{
name: "network-4294967295123123",
expectedErr: ErrParseNetworkName,
},
{
name: "4294967295123123",
expectedErr: ErrParseNetworkName,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
id, err := NetworkID(test.name)
require.ErrorIs(err, test.expectedErr)
require.Equal(test.id, id)
})
}
}
-106
View File
@@ -1,106 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package constants
import (
"time"
"github.com/luxfi/node/utils/compression"
"github.com/luxfi/node/utils/units"
)
const (
// The network must be "tcp", "tcp4", "tcp6", "unix" or "unixpacket".
NetworkType = "tcp"
DefaultMaxMessageSize = 2 * units.MiB
DefaultPingPongTimeout = 30 * time.Second
DefaultPingFrequency = 3 * DefaultPingPongTimeout / 4
DefaultByteSliceCap = 128
MaxContainersLen = int(4 * DefaultMaxMessageSize / 5)
DefaultNetworkPeerListNumValidatorIPs = 15
DefaultNetworkPeerListValidatorGossipSize = 20
DefaultNetworkPeerListNonValidatorGossipSize = 0
DefaultNetworkPeerListPeersGossipSize = 10
DefaultNetworkPeerListGossipFreq = time.Minute
DefaultNetworkPeerListPullGossipFreq = 2 * time.Second
DefaultNetworkPeerListBloomResetFreq = time.Minute
// Inbound Connection Throttling
DefaultInboundConnUpgradeThrottlerCooldown = 10 * time.Second
DefaultInboundThrottlerMaxConnsPerSec = 256
// Outbound Connection Throttling
DefaultOutboundConnectionThrottlingRps = 50
DefaultOutboundConnectionTimeout = 30 * time.Second
// Timeouts
DefaultNetworkInitialTimeout = 5 * time.Second
DefaultNetworkMinimumTimeout = 2 * time.Second
DefaultNetworkMaximumTimeout = 10 * time.Second
DefaultNetworkMaximumInboundTimeout = 10 * time.Second
DefaultNetworkTimeoutHalflife = 5 * time.Minute
DefaultNetworkTimeoutCoefficient = 2
DefaultNetworkReadHandshakeTimeout = 15 * time.Second
DefaultNoIngressValidatorConnectionGracePeriod = 10 * time.Minute
DefaultNetworkCompressionType = compression.TypeZstd
DefaultNetworkMaxClockDifference = time.Minute
DefaultNetworkRequireValidatorToConnect = false
DefaultNetworkPeerReadBufferSize = 8 * units.KiB
DefaultNetworkPeerWriteBufferSize = 8 * units.KiB
DefaultNetworkTCPProxyEnabled = false
// The PROXY protocol specification recommends setting this value to be at
// least 3 seconds to cover a TCP retransmit.
// Ref: https://www.haproxy.org/download/2.3/doc/proxy-protocol.txt
// Specifying a timeout of 0 will actually result in a timeout of 200ms, but
// a timeout of 0 should generally not be provided.
DefaultNetworkTCPProxyReadTimeout = 3 * time.Second
// Benchlist
DefaultBenchlistFailThreshold = 10
DefaultBenchlistDuration = 15 * time.Minute
DefaultBenchlistMinFailingDuration = 2*time.Minute + 30*time.Second
// Router
DefaultConsensusAppConcurrency = 2
DefaultConsensusShutdownTimeout = time.Minute
DefaultFrontierPollFrequency = 100 * time.Millisecond
// Inbound Throttling
DefaultInboundThrottlerAtLargeAllocSize = 6 * units.MiB
DefaultInboundThrottlerVdrAllocSize = 32 * units.MiB
DefaultInboundThrottlerNodeMaxAtLargeBytes = DefaultMaxMessageSize
DefaultInboundThrottlerMaxProcessingMsgsPerNode = 1024
DefaultInboundThrottlerBandwidthRefillRate = 512 * units.KiB
DefaultInboundThrottlerBandwidthMaxBurstSize = DefaultMaxMessageSize
DefaultInboundThrottlerCPUMaxRecheckDelay = 5 * time.Second
DefaultInboundThrottlerDiskMaxRecheckDelay = 5 * time.Second
MinInboundThrottlerMaxRecheckDelay = time.Millisecond
// Outbound Throttling
DefaultOutboundThrottlerAtLargeAllocSize = 32 * units.MiB
DefaultOutboundThrottlerVdrAllocSize = 32 * units.MiB
DefaultOutboundThrottlerNodeMaxAtLargeBytes = DefaultMaxMessageSize
// Network Health
DefaultHealthCheckAveragerHalflife = 10 * time.Second
DefaultNetworkHealthMaxTimeSinceMsgSent = time.Minute
DefaultNetworkHealthMaxTimeSinceMsgReceived = time.Minute
DefaultNetworkHealthMaxPortionSendQueueFill = 0.9
DefaultNetworkHealthMinPeers = 1
DefaultNetworkHealthMaxSendFailRate = .9
// Metrics
DefaultUptimeMetricFreq = 30 * time.Second
// Delays
DefaultNetworkInitialReconnectDelay = time.Second
DefaultNetworkMaxReconnectDelay = time.Minute
)
-41
View File
@@ -1,41 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package constants
import "github.com/luxfi/ids"
const (
PlatformVMName = "platformvm"
XVMName = "xvm"
EVMName = "evm"
XSVMName = "xsvm"
QVMName = "qvm"
)
var (
PlatformVMID = ids.ID{'p', 'l', 'a', 't', 'f', 'o', 'r', 'm', 'v', 'm'}
XVMID = ids.ID{'a', 'v', 'm'}
EVMID = ids.ID{'e', 'v', 'm'}
XSVMID = ids.ID{'x', 's', 'v', 'm'}
QVMID = ids.ID{'q', 'v', 'm'}
)
// VMName returns the name of the VM with the provided ID. If a human readable
// name isn't known, then the formatted ID is returned.
func VMName(vmID ids.ID) string {
switch vmID {
case PlatformVMID:
return PlatformVMName
case XVMID:
return XVMName
case EVMID:
return EVMName
case XSVMID:
return XSVMName
case QVMID:
return QVMName
default:
return vmID.String()
}
}
-35
View File
@@ -1,35 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
package utils
import (
"context"
"time"
)
type detachedContext struct {
ctx context.Context
}
func Detach(ctx context.Context) context.Context {
return &detachedContext{
ctx: ctx,
}
}
func (*detachedContext) Deadline() (time.Time, bool) {
return time.Time{}, false
}
func (*detachedContext) Done() <-chan struct{} {
return nil
}
func (*detachedContext) Err() error {
return nil
}
func (c *detachedContext) Value(key any) any {
return c.ctx.Value(key)
}
-110
View File
@@ -1,110 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bls
import (
"strconv"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/utils"
"github.com/luxfi/node/utils/crypto/bls/blstest"
)
func BenchmarkVerify(b *testing.B) {
privateKey := newKey(require.New(b))
publicKey := publicKey(privateKey)
for _, messageSize := range blstest.BenchmarkSizes {
b.Run(strconv.Itoa(messageSize), func(b *testing.B) {
message := utils.RandomBytes(messageSize)
signature := sign(privateKey, message)
b.ResetTimer()
for n := 0; n < b.N; n++ {
require.True(b, Verify(publicKey, signature, message))
}
})
}
}
func BenchmarkAggregatePublicKeys(b *testing.B) {
keys := make([]*PublicKey, blstest.BiggestBenchmarkSize)
for i := range keys {
privateKey := newKey(require.New(b))
keys[i] = publicKey(privateKey)
}
for _, size := range blstest.BenchmarkSizes {
b.Run(strconv.Itoa(size), func(b *testing.B) {
for n := 0; n < b.N; n++ {
_, err := AggregatePublicKeys(keys[:size])
require.NoError(b, err)
}
})
}
}
func BenchmarkPublicKeyToCompressedBytes(b *testing.B) {
sk := newKey(require.New(b))
pk := publicKey(sk)
b.ResetTimer()
for range b.N {
PublicKeyToCompressedBytes(pk)
}
}
func BenchmarkPublicKeyFromCompressedBytes(b *testing.B) {
sk := newKey(require.New(b))
pk := publicKey(sk)
pkBytes := PublicKeyToCompressedBytes(pk)
b.ResetTimer()
for range b.N {
_, _ = PublicKeyFromCompressedBytes(pkBytes)
}
}
func BenchmarkPublicKeyToUncompressedBytes(b *testing.B) {
sk := newKey(require.New(b))
pk := publicKey(sk)
b.ResetTimer()
for range b.N {
PublicKeyToUncompressedBytes(pk)
}
}
func BenchmarkPublicKeyFromValidUncompressedBytes(b *testing.B) {
sk := newKey(require.New(b))
pk := publicKey(sk)
pkBytes := PublicKeyToUncompressedBytes(pk)
b.ResetTimer()
for range b.N {
_ = PublicKeyFromValidUncompressedBytes(pkBytes)
}
}
func BenchmarkSignatureFromBytes(b *testing.B) {
sk := newKey(require.New(b))
message := utils.RandomBytes(32)
signature := sign(sk, message)
signatureBytes := SignatureToBytes(signature)
b.ResetTimer()
for range b.N {
_, _ = SignatureFromBytes(signatureBytes)
}
}
-70
View File
@@ -1,70 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build cgo
// +build cgo
package bls
import (
"errors"
luxbls "github.com/luxfi/crypto/bls"
)
const PublicKeyLen = luxbls.PublicKeyLen
var (
ErrNoPublicKeys = luxbls.ErrNoPublicKeys
ErrFailedPublicKeyDecompress = luxbls.ErrFailedPublicKeyDecompress
errInvalidPublicKey = errors.New("invalid public key")
errFailedPublicKeyAggregation = errors.New("couldn't aggregate public keys")
)
type PublicKey = luxbls.PublicKey
// PublicKeyToCompressedBytes returns the compressed big-endian format of the
// public key.
func PublicKeyToCompressedBytes(pk *PublicKey) []byte {
return luxbls.PublicKeyToCompressedBytes(pk)
}
// PublicKeyFromCompressedBytes parses the compressed big-endian format of the
// public key into a public key.
func PublicKeyFromCompressedBytes(pkBytes []byte) (*PublicKey, error) {
return luxbls.PublicKeyFromCompressedBytes(pkBytes)
}
// PublicKeyToUncompressedBytes returns the uncompressed big-endian format of
// the public key.
func PublicKeyToUncompressedBytes(key *PublicKey) []byte {
return luxbls.PublicKeyToUncompressedBytes(key)
}
// PublicKeyFromUncompressedBytes parses the uncompressed big-endian format of
// the public key into a public key.
func PublicKeyFromUncompressedBytes(pkBytes []byte) (*PublicKey, error) {
// luxfi/crypto handles uncompressed format
pk := luxbls.PublicKeyFromValidUncompressedBytes(pkBytes)
if pk == nil {
return nil, errInvalidPublicKey
}
return pk, nil
}
// PublicKeyFromValidUncompressedBytes parses the uncompressed big-endian format of
// the public key into a public key without performing validation.
// This should only be used when the public key is known to be valid.
func PublicKeyFromValidUncompressedBytes(pkBytes []byte) *PublicKey {
return luxbls.PublicKeyFromValidUncompressedBytes(pkBytes)
}
// AggregatePublicKeys aggregates a non-zero number of public keys into a
// single aggregated public key.
func AggregatePublicKeys(pks []*PublicKey) (*PublicKey, error) {
if len(pks) == 0 {
return nil, ErrNoPublicKeys
}
// luxfi/crypto should use optimized CGO implementation when available
return luxbls.AggregatePublicKeys(pks)
}
-69
View File
@@ -1,69 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build !cgo
// +build !cgo
package bls
import (
"errors"
luxbls "github.com/luxfi/crypto/bls"
)
const PublicKeyLen = luxbls.PublicKeyLen
var (
ErrNoPublicKeys = luxbls.ErrNoPublicKeys
ErrFailedPublicKeyDecompress = luxbls.ErrFailedPublicKeyDecompress
errInvalidPublicKey = errors.New("invalid public key")
errFailedPublicKeyAggregation = errors.New("couldn't aggregate public keys")
)
type PublicKey = luxbls.PublicKey
// PublicKeyToCompressedBytes returns the compressed big-endian format of the
// public key.
func PublicKeyToCompressedBytes(pk *PublicKey) []byte {
return luxbls.PublicKeyToCompressedBytes(pk)
}
// PublicKeyFromCompressedBytes parses the compressed big-endian format of the
// public key into a public key.
func PublicKeyFromCompressedBytes(pkBytes []byte) (*PublicKey, error) {
return luxbls.PublicKeyFromCompressedBytes(pkBytes)
}
// PublicKeyToUncompressedBytes returns the uncompressed big-endian format of
// the public key.
func PublicKeyToUncompressedBytes(key *PublicKey) []byte {
return luxbls.PublicKeyToUncompressedBytes(key)
}
// PublicKeyFromUncompressedBytes parses the uncompressed big-endian format of
// the public key into a public key.
func PublicKeyFromUncompressedBytes(pkBytes []byte) (*PublicKey, error) {
// luxfi/crypto handles uncompressed format
pk := luxbls.PublicKeyFromValidUncompressedBytes(pkBytes)
if pk == nil {
return nil, errInvalidPublicKey
}
return pk, nil
}
// PublicKeyFromValidUncompressedBytes parses the uncompressed big-endian format of
// the public key into a public key without performing validation.
// This should only be used when the public key is known to be valid.
func PublicKeyFromValidUncompressedBytes(pkBytes []byte) *PublicKey {
return luxbls.PublicKeyFromValidUncompressedBytes(pkBytes)
}
// AggregatePublicKeys aggregates a non-zero number of public keys into a
// single aggregated public key.
func AggregatePublicKeys(pks []*PublicKey) (*PublicKey, error) {
if len(pks) == 0 {
return nil, ErrNoPublicKeys
}
return luxbls.AggregatePublicKeys(pks)
}
-87
View File
@@ -1,87 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bls
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/utils"
)
func newKey(require *require.Assertions) *SecretKey {
sk, err := NewSecretKey()
require.NoError(err)
return sk
}
func publicKey(sk *SecretKey) *PublicKey {
return sk.PublicKey()
}
func sign(sk *SecretKey, msg []byte) *Signature {
sig, err := sk.Sign(msg)
if err != nil {
panic(err)
}
return sig
}
func TestAggregationThreshold(t *testing.T) {
require := require.New(t)
// People in the network would privately generate their secret keys
sk0 := newKey(require)
sk1 := newKey(require)
sk2 := newKey(require)
// All the public keys would be registered on chain
pks := []*PublicKey{
publicKey(sk0),
publicKey(sk1),
publicKey(sk2),
}
// The transaction's unsigned bytes are publicly known.
msg := utils.RandomBytes(1234)
// People may attempt time sign the transaction.
sigs := []*Signature{
sign(sk0, msg),
sign(sk1, msg),
sign(sk2, msg),
}
// The signed transaction would specify which of the public keys have been
// used to sign it. The aggregator should verify each individual signature,
// until it has found a sufficient threshold of valid signatures.
var (
indices = []int{0, 2}
filteredPKs = make([]*PublicKey, len(indices))
filteredSigs = make([]*Signature, len(indices))
)
for i, index := range indices {
pk := pks[index]
filteredPKs[i] = pk
sig := sigs[index]
filteredSigs[i] = sig
valid := Verify(pk, sig, msg)
require.True(valid)
}
// Once the aggregator has the required threshold of signatures, it can
// aggregate the signatures.
aggregatedSig, err := AggregateSignatures(filteredSigs)
require.NoError(err)
// For anyone looking for a proof of the aggregated signature's correctness,
// they can aggregate the public keys and verify the aggregated signature.
aggregatedPK, err := AggregatePublicKeys(filteredPKs)
require.NoError(err)
valid := Verify(aggregatedPK, aggregatedSig, msg)
require.True(valid)
}
-26
View File
@@ -1,26 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package blstest
var (
BenchmarkSizes = []int{
1,
2,
4,
8,
16,
32,
64,
128,
256,
512,
1024,
2048,
4096,
8192,
16384,
32768,
}
BiggestBenchmarkSize = BenchmarkSizes[len(BenchmarkSizes)-1]
)
-29
View File
@@ -1,29 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bls
type Ciphersuite int
const (
CiphersuiteSignature Ciphersuite = iota
CiphersuiteProofOfPossession
)
var ciphersuiteStrings = [...]string{
"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_",
"BLS_POP_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_",
}
var ciphersuiteBytes = [...][]byte{
[]byte(ciphersuiteStrings[0]),
[]byte(ciphersuiteStrings[1]),
}
func (c Ciphersuite) String() string {
return ciphersuiteStrings[c]
}
func (c Ciphersuite) Bytes() []byte {
return ciphersuiteBytes[c]
}
-54
View File
@@ -1,54 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bls
import (
"encoding/base64"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/utils"
)
func TestPublicKeyFromCompressedBytesWrongSize(t *testing.T) {
require := require.New(t)
pkBytes := utils.RandomBytes(PublicKeyLen + 1)
_, err := PublicKeyFromCompressedBytes(pkBytes)
require.ErrorIs(err, ErrFailedPublicKeyDecompress)
}
func TestPublicKeyBytes(t *testing.T) {
require := require.New(t)
pkBytes, err := base64.StdEncoding.DecodeString("h5qt9SPxaCo+vOx6sn+QkkpP7Y40Yja7SEAs2MGb/mZT7oKTWgLogjy5c4/wWIGC")
require.NoError(err)
pk, err := PublicKeyFromCompressedBytes(pkBytes)
require.NoError(err)
pk2Bytes := PublicKeyToCompressedBytes(pk)
require.Equal(pkBytes, pk2Bytes)
}
func TestAggregatePublicKeysNoop(t *testing.T) {
require := require.New(t)
pkBytes, err := base64.StdEncoding.DecodeString("h5qt9SPxaCo+vOx6sn+QkkpP7Y40Yja7SEAs2MGb/mZT7oKTWgLogjy5c4/wWIGC")
require.NoError(err)
pk, err := PublicKeyFromCompressedBytes(pkBytes)
require.NoError(err)
aggPK, err := AggregatePublicKeys([]*PublicKey{pk})
require.NoError(err)
aggPKBytes := PublicKeyToCompressedBytes(aggPK)
require.NoError(err)
require.Equal(pk, aggPK)
require.Equal(pkBytes, aggPKBytes)
}
-82
View File
@@ -1,82 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build cgo
// +build cgo
package bls
import (
"errors"
luxbls "github.com/luxfi/crypto/bls"
)
const SignatureLen = luxbls.SignatureLen
var (
ErrNoSignatures = luxbls.ErrNoSignatures
ErrFailedSignatureDecompress = luxbls.ErrFailedSignatureDecompress
errInvalidSignature = errors.New("invalid signature")
errFailedSignatureAggregation = errors.New("couldn't aggregate signatures")
errInvalidSignatureLen = errors.New("invalid signature length")
)
type (
SecretKey = luxbls.SecretKey
Signature = luxbls.Signature
Signer = luxbls.Signer
)
// NewSecretKey generates a new secret key
func NewSecretKey() (*SecretKey, error) {
return luxbls.NewSecretKey()
}
// SecretKeyFromBytes parses a secret key from bytes
func SecretKeyFromBytes(skBytes []byte) (*SecretKey, error) {
return luxbls.SecretKeyFromBytes(skBytes)
}
// SignatureToBytes returns the compressed big-endian format of the signature.
func SignatureToBytes(sig *Signature) []byte {
return luxbls.SignatureToBytes(sig)
}
// SignatureFromBytes parses the compressed big-endian format of the signature into a signature.
func SignatureFromBytes(sigBytes []byte) (*Signature, error) {
return luxbls.SignatureFromBytes(sigBytes)
}
// SecretKeyToBytes returns the big-endian format of the secret key.
func SecretKeyToBytes(sk *SecretKey) []byte {
return luxbls.SecretKeyToBytes(sk)
}
// Verify verifies that the signature was signed from the given public key on the given message.
func Verify(pk *PublicKey, sig *Signature, msg []byte) bool {
if pk == nil || sig == nil {
return false
}
return luxbls.Verify(pk, sig, msg)
}
// VerifyProofOfPossession verifies that the signature was signed from the
// given public key on the given message using the proof of possession domain
// separation tag.
func VerifyProofOfPossession(pk *PublicKey, sig *Signature, msg []byte) bool {
if pk == nil || sig == nil {
return false
}
// luxfi/crypto should handle PoP verification with CGO optimization
return luxbls.Verify(pk, sig, msg)
}
// AggregateSignatures aggregates a non-zero number of signatures into a single aggregated signature.
func AggregateSignatures(sigs []*Signature) (*Signature, error) {
if len(sigs) == 0 {
return nil, ErrNoSignatures
}
// luxfi/crypto should use optimized CGO implementation when available
return luxbls.AggregateSignatures(sigs)
}
-76
View File
@@ -1,76 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build !cgo
// +build !cgo
package bls
import (
luxbls "github.com/luxfi/crypto/bls"
)
const SignatureLen = luxbls.SignatureLen
var (
ErrFailedSignatureDecompress = luxbls.ErrFailedSignatureDecompress
ErrInvalidSignature = luxbls.ErrInvalidSignature
ErrNoSignatures = luxbls.ErrNoSignatures
ErrFailedSignatureAggregation = luxbls.ErrFailedSignatureAggregation
)
type (
SecretKey = luxbls.SecretKey
Signature = luxbls.Signature
Signer = luxbls.Signer
)
// NewSecretKey generates a new secret key
func NewSecretKey() (*SecretKey, error) {
return luxbls.NewSecretKey()
}
// SecretKeyFromBytes parses a secret key from bytes
func SecretKeyFromBytes(skBytes []byte) (*SecretKey, error) {
return luxbls.SecretKeyFromBytes(skBytes)
}
// SecretKeyToBytes returns the big-endian format of the secret key.
func SecretKeyToBytes(sk *SecretKey) []byte {
return luxbls.SecretKeyToBytes(sk)
}
// SignatureToBytes returns the compressed big-endian format of the signature.
func SignatureToBytes(sig *Signature) []byte {
return luxbls.SignatureToBytes(sig)
}
// SignatureFromBytes parses the compressed big-endian format of the signature
// into a signature.
func SignatureFromBytes(sigBytes []byte) (*Signature, error) {
return luxbls.SignatureFromBytes(sigBytes)
}
// Verify the signature against the provided message and public key.
func Verify(pk *PublicKey, sig *Signature, msg []byte) bool {
return luxbls.Verify(pk, sig, msg)
}
// VerifyProofOfPossession verifies that signature is a valid proof of
// possession of the provided public key.
func VerifyProofOfPossession(pk *PublicKey, sig *Signature, msg []byte) bool {
if pk == nil || sig == nil {
return false
}
// Pure Go implementation should verify with PoP domain
return luxbls.Verify(pk, sig, msg)
}
// AggregateSignatures aggregates a non-zero number of signatures into a single
// aggregated signature.
func AggregateSignatures(sigs []*Signature) (*Signature, error) {
if len(sigs) == 0 {
return nil, ErrNoSignatures
}
return luxbls.AggregateSignatures(sigs)
}
-48
View File
@@ -1,48 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bls
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/utils"
)
func TestSignatureBytes(t *testing.T) {
require := require.New(t)
msg := utils.RandomBytes(1234)
sk := newKey(require)
sig := sign(sk, msg)
sigBytes := SignatureToBytes(sig)
sig2, err := SignatureFromBytes(sigBytes)
require.NoError(err)
sig2Bytes := SignatureToBytes(sig2)
require.Equal(sig, sig2)
require.Equal(sigBytes, sig2Bytes)
}
func TestAggregateSignaturesNoop(t *testing.T) {
require := require.New(t)
msg := utils.RandomBytes(1234)
sk := newKey(require)
sig := sign(sk, msg)
sigBytes := SignatureToBytes(sig)
aggSig, err := AggregateSignatures([]*Signature{sig})
require.NoError(err)
aggSigBytes := SignatureToBytes(aggSig)
require.NoError(err)
require.Equal(sig, aggSig)
require.Equal(sigBytes, aggSigBytes)
}
@@ -1,29 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package localsigner
import (
"strconv"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/utils"
"github.com/luxfi/node/utils/crypto/bls/blstest"
)
func BenchmarkSign(b *testing.B) {
signer := NewSigner(require.New(b))
for _, messageSize := range blstest.BenchmarkSizes {
b.Run(strconv.Itoa(messageSize), func(b *testing.B) {
message := utils.RandomBytes(messageSize)
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, _ = signer.Sign(message)
}
})
}
}
@@ -1,314 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package localsigner
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/utils/crypto/bls"
)
func AggregateAndVerify(publicKeys []*bls.PublicKey, signatures []*bls.Signature, message []byte) (bool, error) {
aggSig, err := bls.AggregateSignatures(signatures)
if err != nil {
return false, err
}
aggPK, err := bls.AggregatePublicKeys(publicKeys)
if err != nil {
return false, err
}
return bls.Verify(aggPK, aggSig, message), nil
}
func NewSigner(require *require.Assertions) *LocalSigner {
sk, err := New()
require.NoError(err)
return sk
}
func mapSlice[T any, U any](arr []T, f func(T) U) []U {
result := make([]U, len(arr))
for i, val := range arr {
result[i] = f(val)
}
return result
}
func TestVerifyValidSignature(t *testing.T) {
require := require.New(t)
signer := NewSigner(require)
msg := []byte("TestVerifyValidSignature local signer")
sig, err := signer.Sign(msg)
require.NoError(err)
isValid := bls.Verify(signer.PublicKey(), sig, msg)
require.True(isValid)
}
func TestVerifyWrongMessageSignature(t *testing.T) {
require := require.New(t)
signer := NewSigner(require)
msg := []byte("TestVerifyWrongMessageSignature local signer")
wrongMsg := []byte("TestVerifyWrongMessageSignature local signer with wrong message")
sig, err := signer.Sign(msg)
require.NoError(err)
isValid := bls.Verify(signer.PublicKey(), sig, wrongMsg)
require.False(isValid)
}
func TestVerifyWrongPubkeySignature(t *testing.T) {
require := require.New(t)
signer := NewSigner(require)
wrongPk := NewSigner(require).pk
msg := []byte("TestVerifyWrongPubkeySignature local signer")
sig, err := signer.Sign(msg)
require.NoError(err)
isValid := bls.Verify(wrongPk, sig, msg)
require.False(isValid)
}
func TestVerifyWrongMessageSignedSignature(t *testing.T) {
require := require.New(t)
signer := NewSigner(require)
msg := []byte("TestVerifyWrongMessageSignedSignature local signer")
wrongMsg := []byte("TestVerifyWrongMessageSignedSignaturelocal local signer with wrong signature")
wrongSig, err := signer.Sign(wrongMsg)
require.NoError(err)
isValid := bls.Verify(signer.PublicKey(), wrongSig, msg)
require.False(isValid)
}
func TestValidAggregation(t *testing.T) {
require := require.New(t)
signers := []*LocalSigner{NewSigner(require), NewSigner(require), NewSigner(require)}
pks := mapSlice(signers, (*LocalSigner).PublicKey)
msg := []byte("TestValidAggregation local signer")
sigs := mapSlice(signers, func(signer *LocalSigner) *bls.Signature {
sig, err := signer.Sign(msg)
require.NoError(err)
return sig
})
isValid, err := AggregateAndVerify(pks, sigs, msg)
require.NoError(err)
require.True(isValid)
}
func TestSingleKeyAggregation(t *testing.T) {
require := require.New(t)
signer := NewSigner(require)
pks := []*bls.PublicKey{signer.PublicKey()}
msg := []byte("TestSingleKeyAggregation local signer")
sig, err := signer.Sign(msg)
require.NoError(err)
isValid, err := AggregateAndVerify(pks, []*bls.Signature{sig}, msg)
require.NoError(err)
require.True(isValid)
}
func TestIncorrectMessageAggregation(t *testing.T) {
require := require.New(t)
signers := []*LocalSigner{NewSigner(require), NewSigner(require), NewSigner(require)}
pks := mapSlice(signers, (*LocalSigner).PublicKey)
msg := []byte("TestIncorrectMessageAggregation local signer")
signatures := mapSlice(signers, func(signer *LocalSigner) *bls.Signature {
sig, err := signer.Sign(msg)
require.NoError(err)
return sig
})
isValid, err := AggregateAndVerify(pks, signatures, []byte("a different message"))
require.NoError(err)
require.False(isValid)
}
func TestDifferentMessageAggregation(t *testing.T) {
require := require.New(t)
signers := []*LocalSigner{NewSigner(require), NewSigner(require), NewSigner(require)}
pks := mapSlice(signers, (*LocalSigner).PublicKey)
msg := []byte("TestDifferentMessagesAggregation local signer")
differentMsg := []byte("TestDifferentMessagesAggregation local signer with different message")
diffSig, err := signers[0].Sign(differentMsg)
require.NoError(err)
sameSigs := mapSlice(signers[1:], func(signer *LocalSigner) *bls.Signature {
sig, err := signer.Sign(msg)
require.NoError(err)
return sig
})
signatures := append([]*bls.Signature{diffSig}, sameSigs...)
isValid, err := AggregateAndVerify(pks, signatures, msg)
require.NoError(err)
require.False(isValid)
}
func TestOneIncorrectPubKeyAggregation(t *testing.T) {
require := require.New(t)
signers := []*LocalSigner{NewSigner(require), NewSigner(require), NewSigner(require)}
pks := mapSlice(signers, (*LocalSigner).PublicKey)
msg := []byte("TestOneIncorrectPubKeyAggregation local signer")
signatures := mapSlice(signers, func(signer *LocalSigner) *bls.Signature {
sig, err := signer.Sign(msg)
require.NoError(err)
return sig
})
// incorrect pubkey
pks[0] = NewSigner(require).PublicKey()
isValid, err := AggregateAndVerify(pks, signatures, msg)
require.NoError(err)
require.False(isValid)
}
func TestMorePubkeysThanSignaturesAggregation(t *testing.T) {
require := require.New(t)
signers := []*LocalSigner{NewSigner(require), NewSigner(require), NewSigner(require)}
pks := mapSlice(signers, (*LocalSigner).PublicKey)
msg := []byte("TestMorePubkeysThanSignatures local signer")
signatures := mapSlice(signers, func(signer *LocalSigner) *bls.Signature {
sig, err := signer.Sign(msg)
require.NoError(err)
return sig
})
// add an extra pubkey
pks = append(pks, NewSigner(require).PublicKey())
isValid, err := AggregateAndVerify(pks, signatures, msg)
require.NoError(err)
require.False(isValid)
}
func TestMoreSignaturesThanPubkeysAggregation(t *testing.T) {
require := require.New(t)
signers := []*LocalSigner{NewSigner(require), NewSigner(require), NewSigner(require)}
pks := mapSlice(signers, (*LocalSigner).PublicKey)
msg := []byte("TestMoreSignaturesThanPubkeys local signer")
signatures := mapSlice(signers, func(signer *LocalSigner) *bls.Signature {
sig, err := signer.Sign(msg)
require.NoError(err)
return sig
})
isValid, err := AggregateAndVerify(pks[1:], signatures, msg)
require.NoError(err)
require.False(isValid)
}
func TestNoPubkeysAggregation(t *testing.T) {
require := require.New(t)
signers := []*LocalSigner{NewSigner(require), NewSigner(require), NewSigner(require)}
msg := []byte("TestNoPubkeysAggregation local signer")
signatures := mapSlice(signers, func(signer *LocalSigner) *bls.Signature {
sig, err := signer.Sign(msg)
require.NoError(err)
return sig
})
isValid, err := AggregateAndVerify(nil, signatures, msg)
require.ErrorIs(err, bls.ErrNoPublicKeys)
require.False(isValid)
}
func TestNoSignaturesAggregation(t *testing.T) {
require := require.New(t)
signers := []*LocalSigner{NewSigner(require), NewSigner(require), NewSigner(require)}
pks := mapSlice(signers, (*LocalSigner).PublicKey)
msg := []byte("TestNoSignaturesAggregation local signer")
isValid, err := AggregateAndVerify(pks, nil, msg)
require.ErrorIs(err, bls.ErrNoSignatures)
require.False(isValid)
}
func TestVerifyValidProofOfPossession(t *testing.T) {
require := require.New(t)
signer := NewSigner(require)
msg := []byte("TestVerifyValidProofOfPossession local signer")
sig, err := signer.SignProofOfPossession(msg)
require.NoError(err)
isValid := bls.VerifyProofOfPossession(signer.PublicKey(), sig, msg)
require.True(isValid)
}
func TestVerifyWrongMessageProofOfPossession(t *testing.T) {
require := require.New(t)
signer := NewSigner(require)
msg := []byte("TestVerifyWrongMessageProofOfPossession local signer")
wrongMsg := []byte("TestVerifyWrongMessageProofOfPossession local signer with wrong message")
sig, err := signer.SignProofOfPossession(msg)
require.NoError(err)
isValid := bls.VerifyProofOfPossession(signer.PublicKey(), sig, wrongMsg)
require.False(isValid)
}
func TestVerifyWrongPubkeyProofOfPossession(t *testing.T) {
require := require.New(t)
correctSigner := NewSigner(require)
wrongSigner := NewSigner(require)
msg := []byte("TestVerifyWrongPubkeyProofOfPossession local signer")
sig, err := correctSigner.SignProofOfPossession(msg)
require.NoError(err)
isValid := bls.VerifyProofOfPossession(wrongSigner.PublicKey(), sig, msg)
require.False(isValid)
}
func TestVerifyWrongMessageSignedProofOfPossession(t *testing.T) {
require := require.New(t)
signer := NewSigner(require)
msg := []byte("TestVerifyWrongMessageSignedProofOfPossession local signer")
wrongMsg := []byte("TestVerifyWrongMessageSignedProofOfPossession local signer with wrong signature")
wrongSig, err := signer.SignProofOfPossession(wrongMsg)
require.NoError(err)
isValid := bls.VerifyProofOfPossession(signer.PublicKey(), wrongSig, msg)
require.False(isValid)
}
@@ -1,68 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build cgo
// +build cgo
package localsigner
import (
"errors"
"github.com/luxfi/node/utils/crypto/bls"
)
var (
ErrFailedSecretKeyDeserialize = errors.New("couldn't deserialize secret key")
_ bls.Signer = (*LocalSigner)(nil)
)
type LocalSigner struct {
sk *bls.SecretKey
pk *bls.PublicKey
}
// NewSecretKey generates a new secret key from the local source of
// cryptographically secure randomness.
func New() (*LocalSigner, error) {
sk, err := bls.NewSecretKey()
if err != nil {
return nil, err
}
pk := sk.PublicKey()
return &LocalSigner{sk: sk, pk: pk}, nil
}
// ToBytes returns the big-endian format of the secret key.
func (s *LocalSigner) ToBytes() []byte {
return bls.SecretKeyToBytes(s.sk)
}
// FromBytes parses the big-endian format of the secret key into a
// secret key.
func FromBytes(skBytes []byte) (*LocalSigner, error) {
sk, err := bls.SecretKeyFromBytes(skBytes)
if err != nil {
return nil, ErrFailedSecretKeyDeserialize
}
pk := sk.PublicKey()
return &LocalSigner{sk: sk, pk: pk}, nil
}
// PublicKey returns the public key that corresponds to this secret
// key.
func (s *LocalSigner) PublicKey() *bls.PublicKey {
return s.pk
}
// Sign [msg] to authorize this message
func (s *LocalSigner) Sign(msg []byte) (*bls.Signature, error) {
return s.sk.Sign(msg)
}
// Sign [msg] to prove the ownership
func (s *LocalSigner) SignProofOfPossession(msg []byte) (*bls.Signature, error) {
return s.sk.SignProofOfPossession(msg)
}
@@ -1,70 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build !cgo
// +build !cgo
package localsigner
import (
luxsigner "github.com/luxfi/crypto/bls/signer/localsigner"
"github.com/luxfi/node/utils/crypto/bls"
)
var (
ErrFailedSecretKeyDeserialize = luxsigner.ErrFailedSecretKeyDeserialize
_ bls.Signer = (*LocalSigner)(nil)
)
// LocalSigner wraps the Lux crypto localsigner
type LocalSigner struct {
signer luxsigner.LocalSigner
}
// New generates a new signer with a random secret key
func New() (*LocalSigner, error) {
signer, err := luxsigner.New()
if err != nil {
return nil, err
}
return &LocalSigner{signer: *signer}, nil
}
// FromBytes parses the big-endian format of the secret key into a LocalSigner
func FromBytes(skBytes []byte) (*LocalSigner, error) {
signer, err := luxsigner.FromBytes(skBytes)
if err != nil {
return nil, err
}
return &LocalSigner{signer: *signer}, nil
}
// ToBytes returns the big-endian format of the secret key
func (s *LocalSigner) ToBytes() []byte {
return s.signer.ToBytes()
}
// PublicKey returns the public key associated with this signer
func (s *LocalSigner) PublicKey() *bls.PublicKey {
pk := s.signer.PublicKey()
return (*bls.PublicKey)(pk)
}
// Sign creates a signature from the provided message
func (s *LocalSigner) Sign(msg []byte) (*bls.Signature, error) {
sig, err := s.signer.Sign(msg)
if err != nil {
return nil, err
}
return (*bls.Signature)(sig), nil
}
// SignProofOfPossession creates a proof of possession signature
func (s *LocalSigner) SignProofOfPossession(msg []byte) (*bls.Signature, error) {
sig, err := s.signer.SignProofOfPossession(msg)
if err != nil {
return nil, err
}
return (*bls.Signature)(sig), nil
}
@@ -1,55 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package localsigner
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/node/utils"
blst "github.com/supranational/blst/bindings/go"
)
const SecretKeyLen = blst.BLST_SCALAR_BYTES
func TestSecretKeyFromBytesZero(t *testing.T) {
require := require.New(t)
var skArr [SecretKeyLen]byte
skBytes := skArr[:]
_, err := FromBytes(skBytes)
require.ErrorIs(err, ErrFailedSecretKeyDeserialize)
}
func TestSecretKeyFromBytesWrongSize(t *testing.T) {
require := require.New(t)
skBytes := utils.RandomBytes(SecretKeyLen + 1)
_, err := FromBytes(skBytes)
require.ErrorIs(err, ErrFailedSecretKeyDeserialize)
}
func TestSecretKeyBytes(t *testing.T) {
require := require.New(t)
msg := utils.RandomBytes(1234)
sk, err := New()
require.NoError(err)
sig, err := sk.Sign(msg)
require.NoError(err)
skBytes := sk.ToBytes()
sk2, err := FromBytes(skBytes)
require.NoError(err)
sig2, err := sk2.Sign(msg)
require.NoError(err)
sk2Bytes := sk2.ToBytes()
require.Equal(sk, sk2)
require.Equal(skBytes, sk2Bytes)
require.Equal(sig, sig2)
}
@@ -1,65 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcsigner
import (
"context"
"google.golang.org/grpc"
"github.com/luxfi/node/utils/crypto/bls"
pb "github.com/luxfi/node/proto/pb/signer"
)
var _ bls.Signer = (*Client)(nil)
type Client struct {
client pb.SignerClient
pk *bls.PublicKey
}
func NewClient(ctx context.Context, conn *grpc.ClientConn) (*Client, error) {
client := pb.NewSignerClient(conn)
pubkeyResponse, err := client.PublicKey(ctx, &pb.PublicKeyRequest{})
if err != nil {
return nil, err
}
pkBytes := pubkeyResponse.GetPublicKey()
pk, err := bls.PublicKeyFromCompressedBytes(pkBytes)
if err != nil {
return nil, err
}
return &Client{
client: client,
pk: pk,
}, nil
}
func (c *Client) PublicKey() *bls.PublicKey {
return c.pk
}
func (c *Client) Sign(message []byte) (*bls.Signature, error) {
resp, err := c.client.Sign(context.TODO(), &pb.SignRequest{Message: message})
if err != nil {
return nil, err
}
signature := resp.GetSignature()
return bls.SignatureFromBytes(signature)
}
func (c *Client) SignProofOfPossession(message []byte) (*bls.Signature, error) {
resp, err := c.client.SignProofOfPossession(context.TODO(), &pb.SignProofOfPossessionRequest{Message: message})
if err != nil {
return nil, err
}
signature := resp.GetSignature()
return bls.SignatureFromBytes(signature)
}
@@ -1,175 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcsigner
import (
"context"
"errors"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"github.com/luxfi/node/proto/pb/signer"
"github.com/luxfi/node/utils/crypto/bls"
"github.com/luxfi/node/utils/crypto/bls/signer/localsigner"
)
var (
validSignatureMsg = []byte("valid")
uncompressedSignatureMsg = []byte("uncompressed")
emptySignatureMsg = []byte("empty")
noSignatureMsg = []byte("none")
)
func newSigner(t *testing.T) *Client {
localSigner, err := localsigner.New()
require.NoError(t, err)
return &Client{
client: &stubClient{
signer: localSigner,
},
pk: localSigner.PublicKey(),
}
}
func TestValidSignature(t *testing.T) {
client := newSigner(t)
sig, err := client.Sign(validSignatureMsg)
require.NoError(t, err)
ok := bls.Verify(client.PublicKey(), sig, validSignatureMsg)
require.True(t, ok)
}
type test struct {
name string
msg []byte
err error
}
var tests = []test{
{
name: "uncompressed",
msg: uncompressedSignatureMsg,
err: bls.ErrFailedSignatureDecompress,
},
{
name: "empty",
msg: emptySignatureMsg,
err: bls.ErrFailedSignatureDecompress,
},
{
name: "none",
msg: noSignatureMsg,
err: bls.ErrFailedSignatureDecompress,
},
}
func TestInvalidSignature(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
client := newSigner(t)
sig, err := client.Sign(test.msg)
require.Nil(t, sig)
require.ErrorIs(t, err, test.err)
})
}
}
func TestValidPOPSignature(t *testing.T) {
client := newSigner(t)
sig, err := client.SignProofOfPossession(validSignatureMsg)
require.NoError(t, err)
ok := bls.VerifyProofOfPossession(client.PublicKey(), sig, validSignatureMsg)
require.True(t, ok)
}
func TestInvalidPOPSignature(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
client := newSigner(t)
sig, err := client.SignProofOfPossession(test.msg)
require.Nil(t, sig)
require.ErrorIs(t, err, test.err)
})
}
}
type stubClient struct {
signer *localsigner.LocalSigner
}
func (*stubClient) PublicKey(_ context.Context, _ *signer.PublicKeyRequest, _ ...grpc.CallOption) (*signer.PublicKeyResponse, error) {
// this function is not used in the tests, however it's required to implement the `signer.SignerClient` interface
return nil, nil
}
// for the `Sign` and `SignProofOfPossession` methods, we're using the same logic where
// we match on the message to determine the type of response we want to test
func (c *stubClient) Sign(_ context.Context, in *signer.SignRequest, _ ...grpc.CallOption) (*signer.SignResponse, error) {
switch string(in.Message) {
case string(validSignatureMsg):
sig, err := c.signer.Sign(in.Message)
if err != nil {
return nil, err
}
return &signer.SignResponse{
Signature: bls.SignatureToBytes(sig),
}, nil
// the client expects a compressed signature so this signature is invalid
// Return malformed bytes to trigger decompression error
case string(uncompressedSignatureMsg):
// Return invalid signature data that will fail decompression
invalidBytes := make([]byte, bls.SignatureLen)
// Fill with invalid data
for i := range invalidBytes {
invalidBytes[i] = 0xFF
}
return &signer.SignResponse{
Signature: invalidBytes,
}, nil
case string(emptySignatureMsg):
return &signer.SignResponse{
Signature: []byte{},
}, nil
case string(noSignatureMsg):
return &signer.SignResponse{}, nil
default:
return nil, errors.New("invalid case")
}
}
// see comments from `Sign` function above
func (c *stubClient) SignProofOfPossession(_ context.Context, in *signer.SignProofOfPossessionRequest, _ ...grpc.CallOption) (*signer.SignProofOfPossessionResponse, error) {
switch string(in.Message) {
case string(validSignatureMsg):
sig, err := c.signer.SignProofOfPossession(in.Message)
if err != nil {
return nil, err
}
return &signer.SignProofOfPossessionResponse{
Signature: bls.SignatureToBytes(sig),
}, nil
case string(uncompressedSignatureMsg):
// Return invalid signature data that will fail decompression
invalidBytes := make([]byte, bls.SignatureLen)
// Fill with invalid data
for i := range invalidBytes {
invalidBytes[i] = 0xFF
}
return &signer.SignProofOfPossessionResponse{
Signature: invalidBytes,
}, nil
case string(emptySignatureMsg):
return &signer.SignProofOfPossessionResponse{
Signature: []byte{},
}, nil
case string(noSignatureMsg):
return &signer.SignProofOfPossessionResponse{}, nil
default:
return nil, errors.New("invalid case")
}
}
-119
View File
@@ -1,119 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package keychain
import (
"errors"
"github.com/luxfi/ids"
"github.com/luxfi/math/set"
)
var (
ErrInvalidIndicesLength = errors.New("number of indices should be greater than 0")
ErrInvalidNumAddrsToDerive = errors.New("number of addresses to derive should be greater than 0")
ErrInvalidNumAddrsDerived = errors.New("incorrect number of ledger derived addresses")
ErrInvalidNumSignatures = errors.New("incorrect number of signatures")
)
// Signer implements functions for a keychain to return its main address and
// to sign a hash
type Signer interface {
SignHash([]byte) ([]byte, error)
Sign([]byte) ([]byte, error)
Address() ids.ShortID
}
// Keychain maintains a set of addresses together with their corresponding
// signers
type Keychain interface {
// The returned Signer can provide a signature for [addr]
Get(addr ids.ShortID) (Signer, bool)
// Returns the set of addresses for which the accessor keeps an associated
// signer
Addresses() set.Set[ids.ShortID]
}
// Ledger interface for hardware wallet support
type Ledger interface {
Address(displayHRP string, addressIndex uint32) (ids.ShortID, error)
SignHash(hash []byte, addressIndex uint32) ([]byte, error)
Sign(hash []byte, addressIndex uint32) ([]byte, error)
SignTransaction(rawUnsignedHash []byte, addressIndices []uint32) ([][]byte, error)
GetAddresses(addressIndices []uint32) ([]ids.ShortID, error)
Disconnect() error
}
// ledgerKeychain is an abstraction of the underlying ledger hardware device,
// to be able to get a signer from a finite set of derived signers
type ledgerKeychain struct {
ledger Ledger
addrs set.Set[ids.ShortID]
addrToIdx map[ids.ShortID]uint32
}
// ledgerSigner is an abstraction of the underlying ledger hardware device,
// capable of extracting its main address and signing a hash
type ledgerSigner struct {
ledger Ledger
idx uint32
addr ids.ShortID
}
// NewLedgerKeychain creates a new ledger keychain
func NewLedgerKeychain(ledger Ledger, indices []uint32) (Keychain, error) {
if len(indices) == 0 {
return nil, ErrInvalidIndicesLength
}
addresses, err := ledger.GetAddresses(indices)
if err != nil {
return nil, err
}
if len(addresses) != len(indices) {
return nil, ErrInvalidNumAddrsDerived
}
addrToIdx := make(map[ids.ShortID]uint32)
addrs := set.Set[ids.ShortID]{}
for i, addr := range addresses {
addrToIdx[addr] = indices[i]
addrs.Add(addr)
}
return &ledgerKeychain{
ledger: ledger,
addrs: addrs,
addrToIdx: addrToIdx,
}, nil
}
func (l *ledgerKeychain) Get(addr ids.ShortID) (Signer, bool) {
idx, ok := l.addrToIdx[addr]
if !ok {
return nil, false
}
return &ledgerSigner{
ledger: l.ledger,
idx: idx,
addr: addr,
}, true
}
func (l *ledgerKeychain) Addresses() set.Set[ids.ShortID] {
return l.addrs
}
func (l *ledgerSigner) SignHash(hash []byte) ([]byte, error) {
return l.ledger.SignHash(hash, l.idx)
}
func (l *ledgerSigner) Sign(hash []byte) ([]byte, error) {
return l.ledger.Sign(hash, l.idx)
}
func (l *ledgerSigner) Address() ids.ShortID {
return l.addr
}
-425
View File
@@ -1,425 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package keychain
import (
"errors"
"testing"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/luxfi/ids"
"github.com/luxfi/node/utils/crypto/keychain/keychainmock"
)
var errTest = errors.New("test")
func TestNewLedgerKeychain(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
addr := ids.GenerateTestShortID()
// user request invalid number of addresses to derive
ledger := keychainmock.NewLedger(ctrl)
_, err := NewLedgerKeychain(ledger, []uint32{})
require.ErrorIs(err, ErrInvalidIndicesLength)
// ledger does not return expected number of derived addresses
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1)
_, err = NewLedgerKeychain(ledger, []uint32{0})
require.ErrorIs(err, ErrInvalidNumAddrsDerived)
// ledger return error when asked for derived addresses
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1)
_, err = NewLedgerKeychain(ledger, []uint32{0})
require.ErrorIs(err, errTest)
// good path
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1)
_, err = NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
}
func TestLedgerKeychain_Addresses(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
addr1 := ids.GenerateTestShortID()
addr2 := ids.GenerateTestShortID()
addr3 := ids.GenerateTestShortID()
// 1 addr
ledger := keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
kc, err := NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
addrs := kc.Addresses()
require.Len(addrs, 1)
require.True(addrs.Contains(addr1))
// multiple addresses
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, []uint32{0, 1, 2})
require.NoError(err)
addrs = kc.Addresses()
require.Len(addrs, 3)
require.Contains(addrs, addr1)
require.Contains(addrs, addr2)
require.Contains(addrs, addr3)
}
func TestLedgerKeychain_Get(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
addr1 := ids.GenerateTestShortID()
addr2 := ids.GenerateTestShortID()
addr3 := ids.GenerateTestShortID()
// 1 addr
ledger := keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
kc, err := NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
_, b := kc.Get(ids.GenerateTestShortID())
require.False(b)
s, b := kc.Get(addr1)
require.Equal(s.Address(), addr1)
require.True(b)
// multiple addresses
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, []uint32{0, 1, 2})
require.NoError(err)
_, b = kc.Get(ids.GenerateTestShortID())
require.False(b)
s, b = kc.Get(addr1)
require.True(b)
require.Equal(s.Address(), addr1)
s, b = kc.Get(addr2)
require.True(b)
require.Equal(s.Address(), addr2)
s, b = kc.Get(addr3)
require.True(b)
require.Equal(s.Address(), addr3)
}
func TestLedgerSigner_SignHash(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
addr1 := ids.GenerateTestShortID()
addr2 := ids.GenerateTestShortID()
addr3 := ids.GenerateTestShortID()
toSign := []byte{1, 2, 3, 4, 5}
expectedSignature1 := []byte{1, 1, 1}
expectedSignature2 := []byte{2, 2, 2}
expectedSignature3 := []byte{3, 3, 3}
// ledger returns an empty signature
ledger := keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
ledger.EXPECT().SignHash(toSign, uint32(0)).Return([]byte{}, nil).Times(1)
kc, err := NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
s, b := kc.Get(addr1)
require.True(b)
signature, err := s.SignHash(toSign)
require.NoError(err)
require.Equal([]byte{}, signature)
// ledger returns an error when asked for signature
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
ledger.EXPECT().SignHash(toSign, uint32(0)).Return(expectedSignature1, errTest).Times(1)
kc, err = NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
s, b = kc.Get(addr1)
require.True(b)
_, err = s.SignHash(toSign)
require.ErrorIs(err, errTest)
// good path 1 addr
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
ledger.EXPECT().SignHash(toSign, uint32(0)).Return(expectedSignature1, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
s, b = kc.Get(addr1)
require.True(b)
signature, err = s.SignHash(toSign)
require.NoError(err)
require.Equal(expectedSignature1, signature)
// good path 3 addr
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1)
ledger.EXPECT().SignHash(toSign, uint32(0)).Return(expectedSignature1, nil).Times(1)
ledger.EXPECT().SignHash(toSign, uint32(1)).Return(expectedSignature2, nil).Times(1)
ledger.EXPECT().SignHash(toSign, uint32(2)).Return(expectedSignature3, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, []uint32{0, 1, 2})
require.NoError(err)
s, b = kc.Get(addr1)
require.True(b)
signature, err = s.SignHash(toSign)
require.NoError(err)
require.Equal(expectedSignature1, signature)
s, b = kc.Get(addr2)
require.True(b)
signature, err = s.SignHash(toSign)
require.NoError(err)
require.Equal(expectedSignature2, signature)
s, b = kc.Get(addr3)
require.True(b)
signature, err = s.SignHash(toSign)
require.NoError(err)
require.Equal(expectedSignature3, signature)
}
func TestNewLedgerKeychainWithIndices(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
addr := ids.GenerateTestShortID()
_ = addr
// user request invalid number of indices
ledger := keychainmock.NewLedger(ctrl)
_, err := NewLedgerKeychain(ledger, []uint32{})
require.ErrorIs(err, ErrInvalidIndicesLength)
// ledger does not return expected number of derived addresses
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{}, nil).Times(1)
_, err = NewLedgerKeychain(ledger, []uint32{0})
require.ErrorIs(err, ErrInvalidNumAddrsDerived)
// ledger return error when asked for derived addresses
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr}, errTest).Times(1)
_, err = NewLedgerKeychain(ledger, []uint32{0})
require.ErrorIs(err, errTest)
// good path
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr}, nil).Times(1)
_, err = NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
}
func TestLedgerKeychainFromIndices_Addresses(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
addr1 := ids.GenerateTestShortID()
addr2 := ids.GenerateTestShortID()
addr3 := ids.GenerateTestShortID()
// 1 addr
ledger := keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
kc, err := NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
addrs := kc.Addresses()
require.Len(addrs, 1)
require.True(addrs.Contains(addr1))
// first 3 addresses
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0, 1, 2}).Return([]ids.ShortID{addr1, addr2, addr3}, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, []uint32{0, 1, 2})
require.NoError(err)
addrs = kc.Addresses()
require.Len(addrs, 3)
require.Contains(addrs, addr1)
require.Contains(addrs, addr2)
require.Contains(addrs, addr3)
// some 3 addresses
indices := []uint32{3, 7, 1}
addresses := []ids.ShortID{addr1, addr2, addr3}
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses(indices).Return(addresses, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, indices)
require.NoError(err)
addrs = kc.Addresses()
require.Len(addrs, len(indices))
require.Contains(addrs, addr1)
require.Contains(addrs, addr2)
require.Contains(addrs, addr3)
// repeated addresses
indices = []uint32{3, 7, 1, 3, 1, 7}
addresses = []ids.ShortID{addr1, addr2, addr3, addr1, addr2, addr3}
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses(indices).Return(addresses, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, indices)
require.NoError(err)
addrs = kc.Addresses()
require.Len(addrs, 3)
require.Contains(addrs, addr1)
require.Contains(addrs, addr2)
require.Contains(addrs, addr3)
}
func TestLedgerKeychainFromIndices_Get(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
addr1 := ids.GenerateTestShortID()
addr2 := ids.GenerateTestShortID()
addr3 := ids.GenerateTestShortID()
// 1 addr
ledger := keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
kc, err := NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
_, b := kc.Get(ids.GenerateTestShortID())
require.False(b)
s, b := kc.Get(addr1)
require.Equal(s.Address(), addr1)
require.True(b)
// some 3 addresses
indices := []uint32{3, 7, 1}
addresses := []ids.ShortID{addr1, addr2, addr3}
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses(indices).Return(addresses, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, indices)
require.NoError(err)
_, b = kc.Get(ids.GenerateTestShortID())
require.False(b)
s, b = kc.Get(addr1)
require.True(b)
require.Equal(s.Address(), addr1)
s, b = kc.Get(addr2)
require.True(b)
require.Equal(s.Address(), addr2)
s, b = kc.Get(addr3)
require.True(b)
require.Equal(s.Address(), addr3)
}
func TestLedgerSignerFromIndices_SignHash(t *testing.T) {
require := require.New(t)
ctrl := gomock.NewController(t)
addr1 := ids.GenerateTestShortID()
addr2 := ids.GenerateTestShortID()
addr3 := ids.GenerateTestShortID()
toSign := []byte{1, 2, 3, 4, 5}
expectedSignature1 := []byte{1, 1, 1}
expectedSignature2 := []byte{2, 2, 2}
expectedSignature3 := []byte{3, 3, 3}
// ledger returns an incorrect number of signatures
ledger := keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
ledger.EXPECT().SignHash(toSign, uint32(0)).Return(expectedSignature1, nil).Times(1)
kc, err := NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
s, b := kc.Get(addr1)
require.True(b)
_, err = s.SignHash(toSign)
require.ErrorIs(err, ErrInvalidNumSignatures)
// ledger returns an error when asked for signature
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
ledger.EXPECT().SignHash(toSign, []uint32{0}).Return([][]byte{expectedSignature1}, errTest).Times(1)
kc, err = NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
s, b = kc.Get(addr1)
require.True(b)
_, err = s.SignHash(toSign)
require.ErrorIs(err, errTest)
// good path 1 addr
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses([]uint32{0}).Return([]ids.ShortID{addr1}, nil).Times(1)
ledger.EXPECT().SignHash(toSign, uint32(0)).Return(expectedSignature1, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, []uint32{0})
require.NoError(err)
s, b = kc.Get(addr1)
require.True(b)
signature, err := s.SignHash(toSign)
require.NoError(err)
require.Equal(expectedSignature1, signature)
// good path some 3 addresses
indices := []uint32{3, 7, 1}
addresses := []ids.ShortID{addr1, addr2, addr3}
ledger = keychainmock.NewLedger(ctrl)
ledger.EXPECT().GetAddresses(indices).Return(addresses, nil).Times(1)
ledger.EXPECT().SignHash(toSign, indices[0]).Return(expectedSignature1, nil).Times(1)
ledger.EXPECT().SignHash(toSign, indices[1]).Return(expectedSignature2, nil).Times(1)
ledger.EXPECT().SignHash(toSign, indices[2]).Return(expectedSignature3, nil).Times(1)
kc, err = NewLedgerKeychain(ledger, indices)
require.NoError(err)
s, b = kc.Get(addr1)
require.True(b)
signature, err = s.SignHash(toSign)
require.NoError(err)
require.Equal(expectedSignature1, signature)
s, b = kc.Get(addr2)
require.True(b)
signature, err = s.SignHash(toSign)
require.NoError(err)
require.Equal(expectedSignature2, signature)
s, b = kc.Get(addr3)
require.True(b)
signature, err = s.SignHash(toSign)
require.NoError(err)
require.Equal(expectedSignature3, signature)
}
@@ -1,130 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/luxfi/node/utils/crypto/keychain (interfaces: Ledger)
//
// Generated by this command:
//
// mockgen -package=keychainmock -destination=keychainmock/ledger.go -mock_names=Ledger=Ledger . Ledger
//
// Package keychainmock is a generated GoMock package.
package keychainmock
import (
reflect "reflect"
ids "github.com/luxfi/ids"
gomock "go.uber.org/mock/gomock"
)
// Ledger is a mock of Ledger interface.
type Ledger struct {
ctrl *gomock.Controller
recorder *LedgerMockRecorder
isgomock struct{}
}
// LedgerMockRecorder is the mock recorder for Ledger.
type LedgerMockRecorder struct {
mock *Ledger
}
// NewLedger creates a new mock instance.
func NewLedger(ctrl *gomock.Controller) *Ledger {
mock := &Ledger{ctrl: ctrl}
mock.recorder = &LedgerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *Ledger) EXPECT() *LedgerMockRecorder {
return m.recorder
}
// Address mocks base method.
func (m *Ledger) Address(displayHRP string, addressIndex uint32) (ids.ShortID, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Address", displayHRP, addressIndex)
ret0, _ := ret[0].(ids.ShortID)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Address indicates an expected call of Address.
func (mr *LedgerMockRecorder) Address(displayHRP, addressIndex any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Address", reflect.TypeOf((*Ledger)(nil).Address), displayHRP, addressIndex)
}
// Disconnect mocks base method.
func (m *Ledger) Disconnect() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Disconnect")
ret0, _ := ret[0].(error)
return ret0
}
// Disconnect indicates an expected call of Disconnect.
func (mr *LedgerMockRecorder) Disconnect() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnect", reflect.TypeOf((*Ledger)(nil).Disconnect))
}
// GetAddresses mocks base method.
func (m *Ledger) GetAddresses(addressIndices []uint32) ([]ids.ShortID, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAddresses", addressIndices)
ret0, _ := ret[0].([]ids.ShortID)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAddresses indicates an expected call of GetAddresses.
func (mr *LedgerMockRecorder) GetAddresses(addressIndices any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddresses", reflect.TypeOf((*Ledger)(nil).GetAddresses), addressIndices)
}
// Sign mocks base method.
func (m *Ledger) Sign(hash []byte, addressIndex uint32) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Sign", hash, addressIndex)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Sign indicates an expected call of Sign.
func (mr *LedgerMockRecorder) Sign(hash, addressIndex any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sign", reflect.TypeOf((*Ledger)(nil).Sign), hash, addressIndex)
}
// SignHash mocks base method.
func (m *Ledger) SignHash(hash []byte, addressIndex uint32) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignHash", hash, addressIndex)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SignHash indicates an expected call of SignHash.
func (mr *LedgerMockRecorder) SignHash(hash, addressIndex any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignHash", reflect.TypeOf((*Ledger)(nil).SignHash), hash, addressIndex)
}
// SignTransaction mocks base method.
func (m *Ledger) SignTransaction(rawUnsignedHash []byte, addressIndices []uint32) ([][]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SignTransaction", rawUnsignedHash, addressIndices)
ret0, _ := ret[0].([][]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SignTransaction indicates an expected call of SignTransaction.
func (mr *LedgerMockRecorder) SignTransaction(rawUnsignedHash, addressIndices any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignTransaction", reflect.TypeOf((*Ledger)(nil).SignTransaction), rawUnsignedHash, addressIndices)
}
@@ -1,6 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package keychain
//go:generate go run go.uber.org/mock/mockgen -package=${GOPACKAGE}mock -destination=${GOPACKAGE}mock/ledger.go -mock_names=Ledger=Ledger . Ledger
-118
View File
@@ -1,118 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ledger
import (
"errors"
"fmt"
"github.com/luxfi/ids"
"github.com/luxfi/node/utils/crypto/keychain"
"github.com/luxfi/node/version"
ledger "github.com/luxfi/ledger-lux-go"
)
// LedgerAdapter wraps ledger.LedgerLux to implement keychain.Ledger interface
type LedgerAdapter struct {
device *ledger.LedgerLux
}
// NewLedger creates a new ledger adapter
func NewLedger() (keychain.Ledger, error) {
device, err := ledger.FindLedgerLuxApp()
if err != nil {
return nil, err
}
return &LedgerAdapter{device: device}, nil
}
// Version returns the app version
func (l *LedgerAdapter) Version() (*version.Semantic, error) {
ver, err := l.device.GetVersion()
if err != nil {
return nil, err
}
return &version.Semantic{
Major: int(ver.Major),
Minor: int(ver.Minor),
Patch: int(ver.Patch),
}, nil
}
// Address returns an address at the given index
func (l *LedgerAdapter) Address(displayHRP string, addressIndex uint32) (ids.ShortID, error) {
pathStr := fmt.Sprintf("44'/9000'/%d'/0/0", addressIndex)
resp, err := l.device.GetPubKey(pathStr, false, displayHRP, "")
if err != nil {
return ids.ShortID{}, err
}
return ids.ShortFromString(resp.Address)
}
// SignHash signs a hash with the given address index
func (l *LedgerAdapter) SignHash(hash []byte, addressIndex uint32) ([]byte, error) {
pathPrefix := fmt.Sprintf("44'/9000'/%d'", addressIndex)
signingPaths := []string{"0/0"}
resp, err := l.device.SignHash(pathPrefix, signingPaths, hash)
if err != nil {
return nil, err
}
// Get the first signature from the response
for _, sig := range resp.Signature {
return sig, nil
}
return nil, errors.New("no signature returned")
}
// Sign signs data with the given address index
func (l *LedgerAdapter) Sign(data []byte, addressIndex uint32) ([]byte, error) {
return l.SignHash(data, addressIndex)
}
// SignTransaction signs a transaction with multiple addresses
func (l *LedgerAdapter) SignTransaction(rawUnsignedHash []byte, addressIndices []uint32) ([][]byte, error) {
// Build signing paths for all addresses
signingPaths := make([]string, len(addressIndices))
for i, idx := range addressIndices {
signingPaths[i] = fmt.Sprintf("%d'/0/0", idx)
}
// Sign with all paths at once
pathPrefix := "44'/9000'"
resp, err := l.device.SignHash(pathPrefix, signingPaths, rawUnsignedHash)
if err != nil {
return nil, err
}
// Extract signatures
sigs := make([][]byte, 0, len(resp.Signature))
for _, sig := range resp.Signature {
sigs = append(sigs, sig)
}
if len(sigs) != len(addressIndices) {
return nil, errors.New("incorrect number of signatures returned")
}
return sigs, nil
}
// GetAddresses returns addresses at the given indices
func (l *LedgerAdapter) GetAddresses(addressIndices []uint32) ([]ids.ShortID, error) {
addrs := make([]ids.ShortID, len(addressIndices))
for i, idx := range addressIndices {
addr, err := l.Address("", idx)
if err != nil {
return nil, err
}
addrs[i] = addr
}
return addrs, nil
}
// Disconnect closes the ledger connection
func (l *LedgerAdapter) Disconnect() error {
return l.device.Close()
}
-80
View File
@@ -1,80 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package ledger
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/node/utils/formatting/address"
"github.com/luxfi/node/utils/hashing"
)
const (
chainAlias = "P"
hrp = "fuji"
)
// TestLedger will be skipped if a ledger is not connected.
func TestLedger(t *testing.T) {
require := require.New(t)
// Initialize Ledger
device, err := NewLedger()
if err != nil {
t.Skip("ledger not detected")
}
// Get version (requires type assertion to *LedgerAdapter)
if adapter, ok := device.(*LedgerAdapter); ok {
version, err := adapter.Version()
require.NoError(err)
t.Logf("version: %s\n", version)
}
// Get Fuji Address
addr, err := device.Address(hrp, 0)
require.NoError(err)
paddr, err := address.Format(chainAlias, hrp, addr[:])
require.NoError(err)
t.Logf("address: %s shortID: %s\n", paddr, addr)
// Get Extended Addresses
addresses, err := device.GetAddresses([]uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
require.NoError(err)
for i, taddr := range addresses {
paddr, err := address.Format(chainAlias, hrp, taddr[:])
require.NoError(err)
t.Logf("address(%d): %s shortID: %s\n", i, paddr, taddr)
// Ensure first derived address matches directly requested address
if i == 0 {
require.Equal(addr, taddr, "address mismatch at index 0")
}
}
// Sign Hash (SignHash now takes single index, returns single signature)
rawHash := hashing.ComputeHash256([]byte{0x1, 0x2, 0x3, 0x4})
indices := []uint32{1, 3}
sigs := make([][]byte, len(indices))
for i, addrIndex := range indices {
sig, err := device.SignHash(rawHash, addrIndex)
require.NoError(err)
sigs[i] = sig
}
require.Len(sigs, 2)
for i, addrIndex := range indices {
sig := sigs[i]
pk, err := secp256k1.RecoverPublicKeyFromHash(rawHash, sig)
require.NoError(err)
require.Equal(addresses[addrIndex], pk.Address())
}
// Disconnect
require.NoError(device.Disconnect())
}
-44
View File
@@ -1,44 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dynamicip
import (
"context"
"fmt"
"io"
"net/http"
"net/netip"
"strings"
"github.com/luxfi/node/utils/ips"
)
var _ Resolver = (*ifConfigResolver)(nil)
// ifConfigResolver resolves our public IP using ifconfig's format.
type ifConfigResolver struct {
url string
}
func (r *ifConfigResolver) Resolve(ctx context.Context) (netip.Addr, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, r.url, nil)
if err != nil {
return netip.Addr{}, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return netip.Addr{}, err
}
defer resp.Body.Close()
ipBytes, err := io.ReadAll(resp.Body)
if err != nil {
// Drop any error to report the original error
return netip.Addr{}, fmt.Errorf("failed to read response from %q: %w", r.url, err)
}
ipStr := strings.TrimSpace(string(ipBytes))
return ips.ParseAddr(ipStr)
}
-18
View File
@@ -1,18 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dynamicip
import "github.com/luxfi/log"
var _ Updater = noUpdater{}
func NewNoUpdater() Updater {
return noUpdater{}
}
type noUpdater struct{}
func (noUpdater) Dispatch(log.Logger) {}
func (noUpdater) Stop() {}
-51
View File
@@ -1,51 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dynamicip
import (
"context"
"errors"
"net"
"net/netip"
"github.com/luxfi/node/utils/ips"
)
const openDNSUrl = "resolver1.opendns.com:53"
var (
errOpenDNSNoIP = errors.New("openDNS returned no ip")
_ Resolver = (*openDNSResolver)(nil)
)
// openDNSResolver resolves our public IP using openDNS
type openDNSResolver struct {
resolver *net.Resolver
}
func newOpenDNSResolver() Resolver {
return &openDNSResolver{
resolver: &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
d := net.Dialer{}
return d.DialContext(ctx, "udp", openDNSUrl)
},
},
}
}
func (r *openDNSResolver) Resolve(ctx context.Context) (netip.Addr, error) {
resolvedIPs, err := r.resolver.LookupIP(ctx, "ip", "myip.opendns.com")
if err != nil {
return netip.Addr{}, err
}
for _, ip := range resolvedIPs {
if addr, ok := ips.AddrFromSlice(ip); ok {
return addr, nil
}
}
return netip.Addr{}, errOpenDNSNoIP
}
-51
View File
@@ -1,51 +0,0 @@
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package dynamicip
import (
"context"
"errors"
"fmt"
"net/netip"
"strings"
)
const (
ifConfigCoURL = "http://ifconfig.co"
ifConfigMeURL = "http://ifconfig.me"
// Note: All of the names below must be lowercase
// because we lowercase the user's input in NewResolver.
// TODO remove either ifConfig or ifConfigCo.
// They do the same thing.
OpenDNSName = "opendns"
IFConfigName = "ifconfig"
IFConfigCoName = "ifconfigco"
IFConfigMeName = "ifconfigme"
)
var errUnknownResolver = errors.New("unknown resolver")
// Resolver resolves our public IP
type Resolver interface {
// Resolve and return our public IP.
Resolve(context.Context) (netip.Addr, error)
}
// Returns a new Resolver that uses the given service
// to resolve our public IP.
// [resolverName] must be one of:
// [OpenDNSName], [IFConfigName], [IFConfigCoName], [IFConfigMeName].
// If [resolverService] isn't one of the above, returns an error
func NewResolver(resolverName string) (Resolver, error) {
switch strings.ToLower(resolverName) {
case OpenDNSName:
return newOpenDNSResolver(), nil
case IFConfigName, IFConfigCoName:
return &ifConfigResolver{url: ifConfigCoURL}, nil
case IFConfigMeName:
return &ifConfigResolver{url: ifConfigMeURL}, nil
default:
return nil, fmt.Errorf("%w: %s", errUnknownResolver, resolverName)
}
}

Some files were not shown because too many files have changed in this diff Show More