Skip to content

Commit

Permalink
go mod tidy, add rand and cache
Browse files Browse the repository at this point in the history
  • Loading branch information
tac0turtle committed Mar 9, 2024
1 parent 564f85c commit 82ed253
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 6 deletions.
3 changes: 2 additions & 1 deletion curves/bls12381/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (
"fmt"
"runtime"

"github.com/cometbft/cometbft/crypto/bls/cache"
blst "github.com/supranational/blst/bindings/go"

"github.com/cosmos/crypto/utils/cache"
)

func init() {
Expand Down
2 changes: 1 addition & 1 deletion curves/bls12381/pubkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"errors"
"fmt"

"github.com/cometbft/cometbft/crypto/bls/cache"
"github.com/cosmos/crypto/utils/cache"
)

const (
Expand Down
2 changes: 1 addition & 1 deletion curves/bls12381/pubkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/cometbft/cometbft/crypto/bls/blst"
blst "github.com/cosmos/crypto/curves/bls12381"
)

func TestPublicKeyFromBytes(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions curves/bls12381/secret_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"errors"
"fmt"

"github.com/cometbft/cometbft/crypto/bls/rand"

blst "github.com/supranational/blst/bindings/go"

"github.com/cosmos/crypto/utils/rand"
)

// bls12SecretKey used in the BLS signature scheme.
Expand Down
2 changes: 1 addition & 1 deletion curves/bls12381/secret_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/cometbft/cometbft/crypto/bls/blst"
blst "github.com/cosmos/crypto/curves/bls12381"
)

func TestMarshalUnmarshal(t *testing.T) {
Expand Down
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/cosmos/crypto

go 1.21.8

require (
github.com/stretchr/testify v1.9.0
github.com/supranational/blst v0.3.11
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
144 changes: 144 additions & 0 deletions utils/cache/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package cache

import (
"errors"
"sync"
)

// EvictCallback is used to get a callback when a cache entry is evicted.
type EvictCallback[K comparable, V any] func(key K, value V)

// LRU implements a non-thread safe fixed size LRU cache.
type LRU[K comparable, V any] struct {
itemsLock sync.RWMutex
evictListLock sync.RWMutex
size int
evictList *lruList[K, V]
items map[K]*entry[K, V]
onEvict EvictCallback[K, V]
getChan chan *entry[K, V]
}

// NewLRU constructs an LRU of the given size.
func NewLRU[K comparable, V any](size int, onEvict EvictCallback[K, V]) (*LRU[K, V], error) {
if size <= 0 {
return nil, errors.New("must provide a positive size")
}
// Initialize the channel buffer size as being 10% of the cache size.
chanSize := size / 10

c := &LRU[K, V]{
size: size,
evictList: newList[K, V](),
items: make(map[K]*entry[K, V]),
onEvict: onEvict,
getChan: make(chan *entry[K, V], chanSize),
}
// Spin off separate go-routine to handle evict list
// operations.
go c.handleGetRequests()
return c, nil
}

// Add adds a value to the cache. Returns true if an eviction occurred.
func (c *LRU[K, V]) Add(key K, value V) (evicted bool) {
// Check for existing item
c.itemsLock.RLock()
if ent, ok := c.items[key]; ok {
c.itemsLock.RUnlock()

c.evictListLock.Lock()
c.evictList.moveToFront(ent)
c.evictListLock.Unlock()
ent.value = value
return false
}
c.itemsLock.RUnlock()

// Add new item
c.evictListLock.Lock()
ent := c.evictList.pushFront(key, value)
c.evictListLock.Unlock()

c.itemsLock.Lock()
c.items[key] = ent
c.itemsLock.Unlock()

c.evictListLock.RLock()
evict := c.evictList.length() > c.size
c.evictListLock.RUnlock()

// Verify size not exceeded
if evict {
c.removeOldest()
}
return evict
}

// Get looks up a key's value from the cache.
func (c *LRU[K, V]) Get(key K) (value V, ok bool) {
c.itemsLock.RLock()
if ent, ok := c.items[key]; ok {
c.itemsLock.RUnlock()

// Make this get function non-blocking for multiple readers.
c.getChan <- ent
return ent.value, true
}
c.itemsLock.RUnlock()
return
}

// Len returns the number of items in the cache.
func (c *LRU[K, V]) Len() int {
c.evictListLock.RLock()
defer c.evictListLock.RUnlock()
return c.evictList.length()
}

// Resize changes the cache size.
func (c *LRU[K, V]) Resize(size int) (evicted int) {
diff := c.Len() - size
if diff < 0 {
diff = 0
}
for i := 0; i < diff; i++ {
c.removeOldest()
}
c.size = size
return diff
}

// removeOldest removes the oldest item from the cache.
func (c *LRU[K, V]) removeOldest() {
c.evictListLock.RLock()
if ent := c.evictList.back(); ent != nil {
c.evictListLock.RUnlock()
c.removeElement(ent)
return
}
c.evictListLock.RUnlock()
}

// removeElement is used to remove a given list element from the cache.
func (c *LRU[K, V]) removeElement(e *entry[K, V]) {
c.evictListLock.Lock()
c.evictList.remove(e)
c.evictListLock.Unlock()

c.itemsLock.Lock()
delete(c.items, e.key)
c.itemsLock.Unlock()
if c.onEvict != nil {
c.onEvict(e.key, e.value)
}
}

func (c *LRU[K, V]) handleGetRequests() {
for {
entry := <-c.getChan
c.evictListLock.Lock()
c.evictList.moveToFront(entry)
c.evictListLock.Unlock()
}
}
123 changes: 123 additions & 0 deletions utils/cache/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE_list file.
package cache

// entry is an LRU entry.
type entry[K comparable, V any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *entry[K, V]

// The list to which this element belongs.
list *lruList[K, V]

// The LRU key of this element.
key K

// The value stored with this element.
value V
}

// lruList represents a doubly linked list.
// The zero value for lruList is an empty list ready to use.
type lruList[K comparable, V any] struct {
root entry[K, V] // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}

// init initializes or clears list l.
func (l *lruList[K, V]) init() *lruList[K, V] {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}

// newList returns an initialized list.
func newList[K comparable, V any]() *lruList[K, V] { return new(lruList[K, V]).init() }

// length returns the number of elements of list l.
// The complexity is O(1).
func (l *lruList[K, V]) length() int { return l.len }

// back returns the last element of list l or nil if the list is empty.
func (l *lruList[K, V]) back() *entry[K, V] {
if l.len == 0 {
return nil
}
return l.root.prev
}

// lazyInit lazily initializes a zero List value.
func (l *lruList[K, V]) lazyInit() {
if l.root.next == nil {
l.init()
}
}

// insert inserts e after at, increments l.len, and returns e.
func (l *lruList[K, V]) insert(e, at *entry[K, V]) *entry[K, V] {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}

// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *lruList[K, V]) insertValue(k K, v V, at *entry[K, V]) *entry[K, V] {
return l.insert(&entry[K, V]{value: v, key: k}, at)
}

// remove removes e from its list, decrements l.len.
func (l *lruList[K, V]) remove(e *entry[K, V]) V {
// If already removed, do nothing.
if e.prev == nil && e.next == nil {
return e.value
}
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
e.list = nil
l.len--

return e.value
}

// move moves e to next to at.
func (*lruList[K, V]) move(e, at *entry[K, V]) {
if e == at {
return
}
e.prev.next = e.next
e.next.prev = e.prev

e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
}

// pushFront inserts a new element e with value v at the front of list l and returns e.
func (l *lruList[K, V]) pushFront(k K, v V) *entry[K, V] {
l.lazyInit()
return l.insertValue(k, v, &l.root)
}

// moveToFront moves element e to the front of list l.
// If e is not an element of l, the list is not modified.
// The element must not be nil.
func (l *lruList[K, V]) moveToFront(e *entry[K, V]) {
if e.list != l || l.root.next == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, &l.root)
}
45 changes: 45 additions & 0 deletions utils/rand/rand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package rand

import (
"crypto/rand"
"encoding/binary"
mrand "math/rand"
"sync"
)

type source struct{}

var lock sync.RWMutex
var _ mrand.Source64 = (*source)(nil) // #nosec G404 -- This ensures we meet the interface

// Seed does nothing when crypto/rand is used as source.
func (_ *source) Seed(_ int64) {}

// Int63 returns uniformly-distributed random (as in CSPRNG) int64 value within [0, 1<<63) range.
// Panics if random generator reader cannot return data.
func (s *source) Int63() int64 {
return int64(s.Uint64() & ^uint64(1<<63))
}

// Uint64 returns uniformly-distributed random (as in CSPRNG) uint64 value within [0, 1<<64) range.
// Panics if random generator reader cannot return data.
func (_ *source) Uint64() (val uint64) {
lock.RLock()
defer lock.RUnlock()
if err := binary.Read(rand.Reader, binary.BigEndian, &val); err != nil {
panic(err)
}
return
}

// Rand is alias for underlying random generator.
type Rand = mrand.Rand // #nosec G404

// NewGenerator returns a new generator that uses random values from crypto/rand as a source
// (cryptographically secure random number generator).
// Panics if crypto/rand input cannot be read.
// Use it for everything where crypto secure non-deterministic randomness is required. Performance
// takes a hit, so use sparingly.
func NewGenerator() *Rand {
return mrand.New(&source{}) // #nosec G404 -- excluded
}

0 comments on commit 82ed253

Please sign in to comment.