Patch for concurrent iterator & others (onto v1.11.6) #386

Closed
roysc wants to merge 1565 commits from v1.11.6-statediff-v5 into master
14 changed files with 711 additions and 147 deletions
Showing only changes of commit 9afc6816d2 - Show all commits

223
common/lru/basiclru.go Normal file
View File

@ -0,0 +1,223 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package lru implements generically-typed LRU caches.
package lru
// BasicLRU is a simple LRU cache.
//
// This type is not safe for concurrent use.
// The zero value is not valid, instances must be created using NewCache.
type BasicLRU[K comparable, V any] struct {
list *list[K]
items map[K]cacheItem[K, V]
cap int
}
type cacheItem[K any, V any] struct {
elem *listElem[K]
value V
}
// NewBasicLRU creates a new LRU cache.
func NewBasicLRU[K comparable, V any](capacity int) BasicLRU[K, V] {
if capacity < 0 {
capacity = 1
}
c := BasicLRU[K, V]{
items: make(map[K]cacheItem[K, V]),
list: newList[K](),
cap: capacity,
}
return c
}
// Add adds a value to the cache. Returns true if an item was evicted to store the new item.
func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) {
item, ok := c.items[key]
if ok {
// Already exists in cache.
item.value = value
c.items[key] = item
c.list.moveToFront(item.elem)
return false
}
var elem *listElem[K]
if c.Len() >= c.cap {
elem = c.list.removeLast()
delete(c.items, elem.v)
evicted = true
} else {
elem = new(listElem[K])
}
// Store the new item.
// Note that, if another item was evicted, we re-use its list element here.
elem.v = key
c.items[key] = cacheItem[K, V]{elem, value}
c.list.pushElem(elem)
return evicted
}
// Contains reports whether the given key exists in the cache.
func (c *BasicLRU[K, V]) Contains(key K) bool {
_, ok := c.items[key]
return ok
}
// Get retrieves a value from the cache. This marks the key as recently used.
func (c *BasicLRU[K, V]) Get(key K) (value V, ok bool) {
item, ok := c.items[key]
if !ok {
return value, false
}
c.list.moveToFront(item.elem)
return item.value, true
}
// GetOldest retrieves the least-recently-used item.
// Note that this does not update the item's recency.
func (c *BasicLRU[K, V]) GetOldest() (key K, value V, ok bool) {
lastElem := c.list.last()
if lastElem == nil {
return key, value, false
}
key = lastElem.v
item := c.items[key]
return key, item.value, true
}
// Len returns the current number of items in the cache.
func (c *BasicLRU[K, V]) Len() int {
return len(c.items)
}
// Peek retrieves a value from the cache, but does not mark the key as recently used.
func (c *BasicLRU[K, V]) Peek(key K) (value V, ok bool) {
item, ok := c.items[key]
return item.value, ok
}
// Purge empties the cache.
func (c *BasicLRU[K, V]) Purge() {
c.list.init()
for k := range c.items {
delete(c.items, k)
}
}
// Remove drops an item from the cache. Returns true if the key was present in cache.
func (c *BasicLRU[K, V]) Remove(key K) bool {
item, ok := c.items[key]
if ok {
delete(c.items, key)
c.list.remove(item.elem)
}
return ok
}
// RemoveOldest drops the least recently used item.
func (c *BasicLRU[K, V]) RemoveOldest() (key K, value V, ok bool) {
lastElem := c.list.last()
if lastElem == nil {
return key, value, false
}
key = lastElem.v
item := c.items[key]
delete(c.items, key)
c.list.remove(lastElem)
return key, item.value, true
}
// Keys returns all keys in the cache.
func (c *BasicLRU[K, V]) Keys() []K {
keys := make([]K, 0, len(c.items))
return c.list.appendTo(keys)
}
// list is a doubly-linked list holding items of type he.
// The zero value is not valid, use newList to create lists.
type list[T any] struct {
root listElem[T]
}
type listElem[T any] struct {
next *listElem[T]
prev *listElem[T]
v T
}
func newList[T any]() *list[T] {
l := new(list[T])
l.init()
return l
}
// init reinitializes the list, making it empty.
func (l *list[T]) init() {
l.root.next = &l.root
l.root.prev = &l.root
}
// push adds an element to the front of the list.
func (l *list[T]) pushElem(e *listElem[T]) {
e.prev = &l.root
e.next = l.root.next
l.root.next = e
e.next.prev = e
}
// moveToFront makes 'node' the head of the list.
func (l *list[T]) moveToFront(e *listElem[T]) {
e.prev.next = e.next
e.next.prev = e.prev
l.pushElem(e)
}
// remove removes an element from the list.
func (l *list[T]) remove(e *listElem[T]) {
e.prev.next = e.next
e.next.prev = e.prev
e.next, e.prev = nil, nil
}
// removeLast removes the last element of the list.
func (l *list[T]) removeLast() *listElem[T] {
last := l.last()
if last != nil {
l.remove(last)
}
return last
}
// last returns the last element of the list, or nil if the list is empty.
func (l *list[T]) last() *listElem[T] {
e := l.root.prev
if e == &l.root {
return nil
}
return e
}
// appendTo appends all list elements to a slice.
func (l *list[T]) appendTo(slice []T) []T {
for e := l.root.prev; e != &l.root; e = e.prev {
slice = append(slice, e.v)
}
return slice
}

240
common/lru/basiclru_test.go Normal file
View File

@ -0,0 +1,240 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lru
import (
"fmt"
"io"
"math/rand"
"testing"
)
// Some of these test cases were adapted
// from https://github.com/hashicorp/golang-lru/blob/master/simplelru/lru_test.go
func TestBasicLRU(t *testing.T) {
cache := NewBasicLRU[int, int](128)
for i := 0; i < 256; i++ {
cache.Add(i, i)
}
if cache.Len() != 128 {
t.Fatalf("bad len: %v", cache.Len())
}
// Check that Keys returns least-recent key first.
keys := cache.Keys()
if len(keys) != 128 {
t.Fatal("wrong Keys() length", len(keys))
}
for i, k := range keys {
v, ok := cache.Peek(k)
if !ok {
t.Fatalf("expected key %d be present", i)
}
if v != k {
t.Fatalf("expected %d == %d", k, v)
}
if v != i+128 {
t.Fatalf("wrong value at key %d: %d, want %d", i, v, i+128)
}
}
for i := 0; i < 128; i++ {
_, ok := cache.Get(i)
if ok {
t.Fatalf("%d should be evicted", i)
}
}
for i := 128; i < 256; i++ {
_, ok := cache.Get(i)
if !ok {
t.Fatalf("%d should not be evicted", i)
}
}
for i := 128; i < 192; i++ {
ok := cache.Remove(i)
if !ok {
t.Fatalf("%d should be in cache", i)
}
ok = cache.Remove(i)
if ok {
t.Fatalf("%d should not be in cache", i)
}
_, ok = cache.Get(i)
if ok {
t.Fatalf("%d should be deleted", i)
}
}
// Request item 192.
cache.Get(192)
// It should be the last item returned by Keys().
for i, k := range cache.Keys() {
if (i < 63 && k != i+193) || (i == 63 && k != 192) {
t.Fatalf("out of order key: %v", k)
}
}
cache.Purge()
if cache.Len() != 0 {
t.Fatalf("bad len: %v", cache.Len())
}
if _, ok := cache.Get(200); ok {
t.Fatalf("should contain nothing")
}
}
func TestBasicLRUAddExistingKey(t *testing.T) {
cache := NewBasicLRU[int, int](1)
cache.Add(1, 1)
cache.Add(1, 2)
v, _ := cache.Get(1)
if v != 2 {
t.Fatal("wrong value:", v)
}
}
// This test checks GetOldest and RemoveOldest.
func TestBasicLRUGetOldest(t *testing.T) {
cache := NewBasicLRU[int, int](128)
for i := 0; i < 256; i++ {
cache.Add(i, i)
}
k, _, ok := cache.GetOldest()
if !ok {
t.Fatalf("missing")
}
if k != 128 {
t.Fatalf("bad: %v", k)
}
k, _, ok = cache.RemoveOldest()
if !ok {
t.Fatalf("missing")
}
if k != 128 {
t.Fatalf("bad: %v", k)
}
k, _, ok = cache.RemoveOldest()
if !ok {
t.Fatalf("missing oldest item")
}
if k != 129 {
t.Fatalf("wrong oldest item: %v", k)
}
}
// Test that Add returns true/false if an eviction occurred
func TestBasicLRUAddReturnValue(t *testing.T) {
cache := NewBasicLRU[int, int](1)
if cache.Add(1, 1) {
t.Errorf("first add shouldn't have evicted")
}
if !cache.Add(2, 2) {
t.Errorf("second add should have evicted")
}
}
// This test verifies that Contains doesn't change item recency.
func TestBasicLRUContains(t *testing.T) {
cache := NewBasicLRU[int, int](2)
cache.Add(1, 1)
cache.Add(2, 2)
if !cache.Contains(1) {
t.Errorf("1 should be in the cache")
}
cache.Add(3, 3)
if cache.Contains(1) {
t.Errorf("Contains should not have updated recency of 1")
}
}
func BenchmarkLRU(b *testing.B) {
var (
capacity = 1000
indexes = make([]int, capacity*20)
keys = make([]string, capacity)
values = make([][]byte, capacity)
)
for i := range indexes {
indexes[i] = rand.Intn(capacity)
}
for i := range keys {
b := make([]byte, 32)
rand.Read(b)
keys[i] = string(b)
rand.Read(b)
values[i] = b
}
var sink []byte
b.Run("Add/BasicLRU", func(b *testing.B) {
cache := NewBasicLRU[int, int](capacity)
for i := 0; i < b.N; i++ {
cache.Add(i, i)
}
})
b.Run("Get/BasicLRU", func(b *testing.B) {
cache := NewBasicLRU[string, []byte](capacity)
for i := 0; i < capacity; i++ {
index := indexes[i]
cache.Add(keys[index], values[index])
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
k := keys[indexes[i%len(indexes)]]
v, ok := cache.Get(k)
if ok {
sink = v
}
}
})
// // vs. github.com/hashicorp/golang-lru/simplelru
// b.Run("Add/simplelru.LRU", func(b *testing.B) {
// cache, _ := simplelru.NewLRU(capacity, nil)
// for i := 0; i < b.N; i++ {
// cache.Add(i, i)
// }
// })
// b.Run("Get/simplelru.LRU", func(b *testing.B) {
// cache, _ := simplelru.NewLRU(capacity, nil)
// for i := 0; i < capacity; i++ {
// index := indexes[i]
// cache.Add(keys[index], values[index])
// }
//
// b.ResetTimer()
// for i := 0; i < b.N; i++ {
// k := keys[indexes[i%len(indexes)]]
// v, ok := cache.Get(k)
// if ok {
// sink = v.([]byte)
// }
// }
// })
fmt.Fprintln(io.Discard, sink)
}

View File

@ -19,33 +19,32 @@ package lru
import ( import (
"math" "math"
"sync" "sync"
"github.com/ethereum/go-ethereum/common"
"github.com/hashicorp/golang-lru/simplelru"
) )
// SizeConstrainedLRU is a wrapper around simplelru.LRU. The simplelru.LRU is capable // blobType is the type constraint for values stored in SizeConstrainedCache.
// of item-count constraints, but is not capable of enforcing a byte-size constraint, type blobType interface {
// hence this wrapper. ~[]byte | ~string
}
// SizeConstrainedCache is a cache where capacity is in bytes (instead of item count). When the cache
// is at capacity, and a new item is added, older items are evicted until the size
// constraint is met.
//
// OBS: This cache assumes that items are content-addressed: keys are unique per content. // OBS: This cache assumes that items are content-addressed: keys are unique per content.
// In other words: two Add(..) with the same key K, will always have the same value V. // In other words: two Add(..) with the same key K, will always have the same value V.
type SizeConstrainedLRU struct { type SizeConstrainedCache[K comparable, V blobType] struct {
size uint64 size uint64
maxSize uint64 maxSize uint64
lru *simplelru.LRU lru BasicLRU[K, V]
lock sync.Mutex lock sync.Mutex
} }
// NewSizeConstrainedLRU creates a new SizeConstrainedLRU. // NewSizeConstrainedCache creates a new size-constrained LRU cache.
func NewSizeConstrainedLRU(max uint64) *SizeConstrainedLRU { func NewSizeConstrainedCache[K comparable, V blobType](maxSize uint64) *SizeConstrainedCache[K, V] {
lru, err := simplelru.NewLRU(math.MaxInt, nil) return &SizeConstrainedCache[K, V]{
if err != nil {
panic(err)
}
return &SizeConstrainedLRU{
size: 0, size: 0,
maxSize: max, maxSize: maxSize,
lru: lru, lru: NewBasicLRU[K, V](math.MaxInt),
} }
} }
@ -53,7 +52,7 @@ func NewSizeConstrainedLRU(max uint64) *SizeConstrainedLRU {
// OBS: This cache assumes that items are content-addressed: keys are unique per content. // OBS: This cache assumes that items are content-addressed: keys are unique per content.
// In other words: two Add(..) with the same key K, will always have the same value V. // In other words: two Add(..) with the same key K, will always have the same value V.
// OBS: The value is _not_ copied on Add, so the caller must not modify it afterwards. // OBS: The value is _not_ copied on Add, so the caller must not modify it afterwards.
func (c *SizeConstrainedLRU) Add(key common.Hash, value []byte) (evicted bool) { func (c *SizeConstrainedCache[K, V]) Add(key K, value V) (evicted bool) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
@ -68,7 +67,7 @@ func (c *SizeConstrainedLRU) Add(key common.Hash, value []byte) (evicted bool) {
// list is now empty. Break // list is now empty. Break
break break
} }
targetSize -= uint64(len(v.([]byte))) targetSize -= uint64(len(v))
} }
c.size = targetSize c.size = targetSize
} }
@ -77,12 +76,9 @@ func (c *SizeConstrainedLRU) Add(key common.Hash, value []byte) (evicted bool) {
} }
// Get looks up a key's value from the cache. // Get looks up a key's value from the cache.
func (c *SizeConstrainedLRU) Get(key common.Hash) []byte { func (c *SizeConstrainedCache[K, V]) Get(key K) (V, bool) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
if v, ok := c.lru.Get(key); ok { return c.lru.Get(key)
return v.([]byte)
}
return nil
} }

View File

@ -20,22 +20,21 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"testing" "testing"
"github.com/ethereum/go-ethereum/common"
) )
func mkHash(i int) common.Hash { type testKey [8]byte
h := make([]byte, 32)
binary.LittleEndian.PutUint64(h, uint64(i)) func mkKey(i int) (key testKey) {
return common.BytesToHash(h) binary.LittleEndian.PutUint64(key[:], uint64(i))
return key
} }
func TestBlobLru(t *testing.T) { func TestSizeConstrainedCache(t *testing.T) {
lru := NewSizeConstrainedLRU(100) lru := NewSizeConstrainedCache[testKey, []byte](100)
var want uint64 var want uint64
// Add 11 items of 10 byte each. First item should be swapped out // Add 11 items of 10 byte each. First item should be swapped out
for i := 0; i < 11; i++ { for i := 0; i < 11; i++ {
k := mkHash(i) k := mkKey(i)
v := fmt.Sprintf("value-%04d", i) v := fmt.Sprintf("value-%04d", i)
lru.Add(k, []byte(v)) lru.Add(k, []byte(v))
want += uint64(len(v)) want += uint64(len(v))
@ -48,17 +47,17 @@ func TestBlobLru(t *testing.T) {
} }
// Zero:th should be evicted // Zero:th should be evicted
{ {
k := mkHash(0) k := mkKey(0)
if val := lru.Get(k); val != nil { if _, ok := lru.Get(k); ok {
t.Fatalf("should be evicted: %v", k) t.Fatalf("should be evicted: %v", k)
} }
} }
// Elems 1-11 should be present // Elems 1-11 should be present
for i := 1; i < 11; i++ { for i := 1; i < 11; i++ {
k := mkHash(i) k := mkKey(i)
want := fmt.Sprintf("value-%04d", i) want := fmt.Sprintf("value-%04d", i)
have := lru.Get(k) have, ok := lru.Get(k)
if have == nil { if !ok {
t.Fatalf("missing key %v", k) t.Fatalf("missing key %v", k)
} }
if string(have) != want { if string(have) != want {
@ -67,26 +66,26 @@ func TestBlobLru(t *testing.T) {
} }
} }
// TestBlobLruOverflow tests what happens when inserting an element exceeding // This test adds inserting an element exceeding the max size.
// the max size func TestSizeConstrainedCacheOverflow(t *testing.T) {
func TestBlobLruOverflow(t *testing.T) { lru := NewSizeConstrainedCache[testKey, []byte](100)
lru := NewSizeConstrainedLRU(100)
// Add 10 items of 10 byte each, filling the cache // Add 10 items of 10 byte each, filling the cache
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
k := mkHash(i) k := mkKey(i)
v := fmt.Sprintf("value-%04d", i) v := fmt.Sprintf("value-%04d", i)
lru.Add(k, []byte(v)) lru.Add(k, []byte(v))
} }
// Add one single large elem. We expect it to swap out all entries. // Add one single large elem. We expect it to swap out all entries.
{ {
k := mkHash(1337) k := mkKey(1337)
v := make([]byte, 200) v := make([]byte, 200)
lru.Add(k, v) lru.Add(k, v)
} }
// Elems 0-9 should be missing // Elems 0-9 should be missing
for i := 1; i < 10; i++ { for i := 1; i < 10; i++ {
k := mkHash(i) k := mkKey(i)
if val := lru.Get(k); val != nil { if _, ok := lru.Get(k); ok {
t.Fatalf("should be evicted: %v", k) t.Fatalf("should be evicted: %v", k)
} }
} }
@ -97,7 +96,7 @@ func TestBlobLruOverflow(t *testing.T) {
// Adding one small item should swap out the large one // Adding one small item should swap out the large one
{ {
i := 0 i := 0
k := mkHash(i) k := mkKey(i)
v := fmt.Sprintf("value-%04d", i) v := fmt.Sprintf("value-%04d", i)
lru.Add(k, []byte(v)) lru.Add(k, []byte(v))
if have, want := lru.size, uint64(10); have != want { if have, want := lru.size, uint64(10); have != want {
@ -106,17 +105,51 @@ func TestBlobLruOverflow(t *testing.T) {
} }
} }
// TestBlobLruSameItem tests what happens when inserting the same k/v multiple times. // This checks what happens when inserting the same k/v multiple times.
func TestBlobLruSameItem(t *testing.T) { func TestSizeConstrainedCacheSameItem(t *testing.T) {
lru := NewSizeConstrainedLRU(100) lru := NewSizeConstrainedCache[testKey, []byte](100)
// Add one 10 byte-item 10 times
k := mkHash(0) // Add one 10 byte-item 10 times.
k := mkKey(0)
v := fmt.Sprintf("value-%04d", 0) v := fmt.Sprintf("value-%04d", 0)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
lru.Add(k, []byte(v)) lru.Add(k, []byte(v))
} }
// The size should be accurate
// The size should be accurate.
if have, want := lru.size, uint64(10); have != want { if have, want := lru.size, uint64(10); have != want {
t.Fatalf("size wrong, have %d want %d", have, want) t.Fatalf("size wrong, have %d want %d", have, want)
} }
} }
// This tests that empty/nil values are handled correctly.
func TestSizeConstrainedCacheEmpties(t *testing.T) {
lru := NewSizeConstrainedCache[testKey, []byte](100)
// This test abuses the lru a bit, using different keys for identical value(s).
for i := 0; i < 10; i++ {
lru.Add(testKey{byte(i)}, []byte{})
lru.Add(testKey{byte(255 - i)}, nil)
}
// The size should not count, only the values count. So this could be a DoS
// since it basically has no cap, and it is intentionally overloaded with
// different-keyed 0-length values.
if have, want := lru.size, uint64(0); have != want {
t.Fatalf("size wrong, have %d want %d", have, want)
}
for i := 0; i < 10; i++ {
if v, ok := lru.Get(testKey{byte(i)}); !ok {
t.Fatalf("test %d: expected presence", i)
} else if v == nil {
t.Fatalf("test %d, v is nil", i)
}
if v, ok := lru.Get(testKey{byte(255 - i)}); !ok {
t.Fatalf("test %d: expected presence", i)
} else if v != nil {
t.Fatalf("test %d, v is not nil", i)
}
}
}

95
common/lru/lru.go Normal file
View File

@ -0,0 +1,95 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lru
import "sync"
// Cache is a LRU cache.
// This type is safe for concurrent use.
type Cache[K comparable, V any] struct {
cache BasicLRU[K, V]
mu sync.Mutex
}
// NewCache creates an LRU cache.
func NewCache[K comparable, V any](capacity int) *Cache[K, V] {
return &Cache[K, V]{cache: NewBasicLRU[K, V](capacity)}
}
// Add adds a value to the cache. Returns true if an item was evicted to store the new item.
func (c *Cache[K, V]) Add(key K, value V) (evicted bool) {
c.mu.Lock()
defer c.mu.Unlock()
return c.cache.Add(key, value)
}
// Contains reports whether the given key exists in the cache.
func (c *Cache[K, V]) Contains(key K) bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.cache.Contains(key)
}
// Get retrieves a value from the cache. This marks the key as recently used.
func (c *Cache[K, V]) Get(key K) (value V, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
return c.cache.Get(key)
}
// Len returns the current number of items in the cache.
func (c *Cache[K, V]) Len() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.cache.Len()
}
// Peek retrieves a value from the cache, but does not mark the key as recently used.
func (c *Cache[K, V]) Peek(key K) (value V, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
return c.cache.Peek(key)
}
// Purge empties the cache.
func (c *Cache[K, V]) Purge() {
c.mu.Lock()
defer c.mu.Unlock()
c.cache.Purge()
}
// Remove drops an item from the cache. Returns true if the key was present in cache.
func (c *Cache[K, V]) Remove(key K) bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.cache.Remove(key)
}
// Keys returns all keys of items currently in the LRU.
func (c *Cache[K, V]) Keys() []K {
c.mu.Lock()
defer c.mu.Unlock()
return c.cache.Keys()
}

View File

@ -30,6 +30,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
@ -45,8 +46,8 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
lru "github.com/hashicorp/golang-lru"
) )
var ( var (
@ -200,12 +201,14 @@ type BlockChain struct {
currentSafeBlock atomic.Value // Current safe head currentSafeBlock atomic.Value // Current safe head
stateCache state.Database // State database to reuse between imports (contains state cache) stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies bodyCache *lru.Cache[common.Hash, *types.Body]
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue]
receiptsCache *lru.Cache // Cache for the most recent receipts per block receiptsCache *lru.Cache[common.Hash, []*types.Receipt]
blockCache *lru.Cache // Cache for the most recent entire blocks blockCache *lru.Cache[common.Hash, *types.Block]
txLookupCache *lru.Cache // Cache for the most recent transaction lookup data. txLookupCache *lru.Cache[common.Hash, *rawdb.LegacyTxLookupEntry]
futureBlocks *lru.Cache // future blocks are blocks added for later processing
// future blocks are blocks added for later processing
futureBlocks *lru.Cache[common.Hash, *types.Block]
wg sync.WaitGroup // wg sync.WaitGroup //
quit chan struct{} // shutdown signal, closed in Stop. quit chan struct{} // shutdown signal, closed in Stop.
@ -227,12 +230,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
if cacheConfig == nil { if cacheConfig == nil {
cacheConfig = defaultCacheConfig cacheConfig = defaultCacheConfig
} }
bodyCache, _ := lru.New(bodyCacheLimit)
bodyRLPCache, _ := lru.New(bodyCacheLimit)
receiptsCache, _ := lru.New(receiptsCacheLimit)
blockCache, _ := lru.New(blockCacheLimit)
txLookupCache, _ := lru.New(txLookupCacheLimit)
futureBlocks, _ := lru.New(maxFutureBlocks)
// Setup the genesis block, commit the provided genesis specification // Setup the genesis block, commit the provided genesis specification
// to database if the genesis block is not present yet, or load the // to database if the genesis block is not present yet, or load the
@ -261,12 +258,12 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
}), }),
quit: make(chan struct{}), quit: make(chan struct{}),
chainmu: syncx.NewClosableMutex(), chainmu: syncx.NewClosableMutex(),
bodyCache: bodyCache, bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
bodyRLPCache: bodyRLPCache, bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit),
receiptsCache: receiptsCache, receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit),
blockCache: blockCache, blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit),
txLookupCache: txLookupCache, txLookupCache: lru.NewCache[common.Hash, *rawdb.LegacyTxLookupEntry](txLookupCacheLimit),
futureBlocks: futureBlocks, futureBlocks: lru.NewCache[common.Hash, *types.Block](maxFutureBlocks),
engine: engine, engine: engine,
vmConfig: vmConfig, vmConfig: vmConfig,
} }
@ -957,7 +954,7 @@ func (bc *BlockChain) procFutureBlocks() {
blocks := make([]*types.Block, 0, bc.futureBlocks.Len()) blocks := make([]*types.Block, 0, bc.futureBlocks.Len())
for _, hash := range bc.futureBlocks.Keys() { for _, hash := range bc.futureBlocks.Keys() {
if block, exist := bc.futureBlocks.Peek(hash); exist { if block, exist := bc.futureBlocks.Peek(hash); exist {
blocks = append(blocks, block.(*types.Block)) blocks = append(blocks, block)
} }
} }
if len(blocks) > 0 { if len(blocks) > 0 {

View File

@ -96,8 +96,7 @@ func (bc *BlockChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
func (bc *BlockChain) GetBody(hash common.Hash) *types.Body { func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
// Short circuit if the body's already in the cache, retrieve otherwise // Short circuit if the body's already in the cache, retrieve otherwise
if cached, ok := bc.bodyCache.Get(hash); ok { if cached, ok := bc.bodyCache.Get(hash); ok {
body := cached.(*types.Body) return cached
return body
} }
number := bc.hc.GetBlockNumber(hash) number := bc.hc.GetBlockNumber(hash)
if number == nil { if number == nil {
@ -117,7 +116,7 @@ func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {
func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue { func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {
// Short circuit if the body's already in the cache, retrieve otherwise // Short circuit if the body's already in the cache, retrieve otherwise
if cached, ok := bc.bodyRLPCache.Get(hash); ok { if cached, ok := bc.bodyRLPCache.Get(hash); ok {
return cached.(rlp.RawValue) return cached
} }
number := bc.hc.GetBlockNumber(hash) number := bc.hc.GetBlockNumber(hash)
if number == nil { if number == nil {
@ -159,7 +158,7 @@ func (bc *BlockChain) HasFastBlock(hash common.Hash, number uint64) bool {
func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
// Short circuit if the block's already in the cache, retrieve otherwise // Short circuit if the block's already in the cache, retrieve otherwise
if block, ok := bc.blockCache.Get(hash); ok { if block, ok := bc.blockCache.Get(hash); ok {
return block.(*types.Block) return block
} }
block := rawdb.ReadBlock(bc.db, hash, number) block := rawdb.ReadBlock(bc.db, hash, number)
if block == nil { if block == nil {
@ -211,7 +210,7 @@ func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*type
// GetReceiptsByHash retrieves the receipts for all transactions in a given block. // GetReceiptsByHash retrieves the receipts for all transactions in a given block.
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
if receipts, ok := bc.receiptsCache.Get(hash); ok { if receipts, ok := bc.receiptsCache.Get(hash); ok {
return receipts.(types.Receipts) return receipts
} }
number := rawdb.ReadHeaderNumber(bc.db, hash) number := rawdb.ReadHeaderNumber(bc.db, hash)
if number == nil { if number == nil {
@ -255,7 +254,7 @@ func (bc *BlockChain) GetAncestor(hash common.Hash, number, ancestor uint64, max
func (bc *BlockChain) GetTransactionLookup(hash common.Hash) *rawdb.LegacyTxLookupEntry { func (bc *BlockChain) GetTransactionLookup(hash common.Hash) *rawdb.LegacyTxLookupEntry {
// Short circuit if the txlookup already in the cache, retrieve otherwise // Short circuit if the txlookup already in the cache, retrieve otherwise
if lookup, exist := bc.txLookupCache.Get(hash); exist { if lookup, exist := bc.txLookupCache.Get(hash); exist {
return lookup.(*rawdb.LegacyTxLookupEntry) return lookup
} }
tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash) tx, blockHash, blockNumber, txIndex := rawdb.ReadTransaction(bc.db, hash)
if tx == nil { if tx == nil {

View File

@ -27,6 +27,7 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -34,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
lru "github.com/hashicorp/golang-lru"
) )
const ( const (
@ -64,9 +64,9 @@ type HeaderChain struct {
currentHeader atomic.Value // Current head of the header chain (may be above the block chain!) currentHeader atomic.Value // Current head of the header chain (may be above the block chain!)
currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time) currentHeaderHash common.Hash // Hash of the current head of the header chain (prevent recomputing all the time)
headerCache *lru.Cache // Cache for the most recent block headers headerCache *lru.Cache[common.Hash, *types.Header]
tdCache *lru.Cache // Cache for the most recent block total difficulties tdCache *lru.Cache[common.Hash, *big.Int] // most recent total difficulties
numberCache *lru.Cache // Cache for the most recent block numbers numberCache *lru.Cache[common.Hash, uint64] // most recent block numbers
procInterrupt func() bool procInterrupt func() bool
@ -77,10 +77,6 @@ type HeaderChain struct {
// NewHeaderChain creates a new HeaderChain structure. ProcInterrupt points // NewHeaderChain creates a new HeaderChain structure. ProcInterrupt points
// to the parent's interrupt semaphore. // to the parent's interrupt semaphore.
func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine consensus.Engine, procInterrupt func() bool) (*HeaderChain, error) { func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine consensus.Engine, procInterrupt func() bool) (*HeaderChain, error) {
headerCache, _ := lru.New(headerCacheLimit)
tdCache, _ := lru.New(tdCacheLimit)
numberCache, _ := lru.New(numberCacheLimit)
// Seed a fast but crypto originating random generator // Seed a fast but crypto originating random generator
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
if err != nil { if err != nil {
@ -89,9 +85,9 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
hc := &HeaderChain{ hc := &HeaderChain{
config: config, config: config,
chainDb: chainDb, chainDb: chainDb,
headerCache: headerCache, headerCache: lru.NewCache[common.Hash, *types.Header](headerCacheLimit),
tdCache: tdCache, tdCache: lru.NewCache[common.Hash, *big.Int](tdCacheLimit),
numberCache: numberCache, numberCache: lru.NewCache[common.Hash, uint64](numberCacheLimit),
procInterrupt: procInterrupt, procInterrupt: procInterrupt,
rand: mrand.New(mrand.NewSource(seed.Int64())), rand: mrand.New(mrand.NewSource(seed.Int64())),
engine: engine, engine: engine,
@ -115,8 +111,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
// from the cache or database // from the cache or database
func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 { func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
if cached, ok := hc.numberCache.Get(hash); ok { if cached, ok := hc.numberCache.Get(hash); ok {
number := cached.(uint64) return &cached
return &number
} }
number := rawdb.ReadHeaderNumber(hc.chainDb, hash) number := rawdb.ReadHeaderNumber(hc.chainDb, hash)
if number != nil { if number != nil {
@ -442,7 +437,7 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma
func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int { func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
// Short circuit if the td's already in the cache, retrieve otherwise // Short circuit if the td's already in the cache, retrieve otherwise
if cached, ok := hc.tdCache.Get(hash); ok { if cached, ok := hc.tdCache.Get(hash); ok {
return cached.(*big.Int) return cached
} }
td := rawdb.ReadTd(hc.chainDb, hash, number) td := rawdb.ReadTd(hc.chainDb, hash, number)
if td == nil { if td == nil {
@ -458,7 +453,7 @@ func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int {
func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header { func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header {
// Short circuit if the header's already in the cache, retrieve otherwise // Short circuit if the header's already in the cache, retrieve otherwise
if header, ok := hc.headerCache.Get(hash); ok { if header, ok := hc.headerCache.Get(hash); ok {
return header.(*types.Header) return header
} }
header := rawdb.ReadHeader(hc.chainDb, hash, number) header := rawdb.ReadHeader(hc.chainDb, hash, number)
if header == nil { if header == nil {
@ -525,10 +520,9 @@ func (hc *HeaderChain) GetHeadersFrom(number, count uint64) []rlp.RawValue {
if !ok { if !ok {
break break
} }
h := header.(*types.Header) rlpData, _ := rlp.EncodeToBytes(header)
rlpData, _ := rlp.EncodeToBytes(h)
headers = append(headers, rlpData) headers = append(headers, rlpData)
hash = h.ParentHash hash = header.ParentHash
count-- count--
number-- number--
} }

View File

@ -21,12 +21,11 @@ import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
lru2 "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
lru "github.com/hashicorp/golang-lru"
) )
const ( const (
@ -130,20 +129,19 @@ func NewDatabase(db ethdb.Database) Database {
// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a // is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a
// large memory cache. // large memory cache.
func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database {
csc, _ := lru.New(codeSizeCacheSize)
return &cachingDB{ return &cachingDB{
db: trie.NewDatabaseWithConfig(db, config), db: trie.NewDatabaseWithConfig(db, config),
disk: db, disk: db,
codeSizeCache: csc, codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
codeCache: lru2.NewSizeConstrainedLRU(codeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
} }
} }
type cachingDB struct { type cachingDB struct {
db *trie.Database db *trie.Database
disk ethdb.KeyValueStore disk ethdb.KeyValueStore
codeSizeCache *lru.Cache codeSizeCache *lru.Cache[common.Hash, int]
codeCache *lru2.SizeConstrainedLRU codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
} }
// OpenTrie opens the main account trie at a specific root hash. // OpenTrie opens the main account trie at a specific root hash.
@ -176,10 +174,11 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {
// ContractCode retrieves a particular contract's code. // ContractCode retrieves a particular contract's code.
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) { func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
if code := db.codeCache.Get(codeHash); len(code) > 0 { code, _ := db.codeCache.Get(codeHash)
if len(code) > 0 {
return code, nil return code, nil
} }
code := rawdb.ReadCode(db.disk, codeHash) code = rawdb.ReadCode(db.disk, codeHash)
if len(code) > 0 { if len(code) > 0 {
db.codeCache.Add(codeHash, code) db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code)) db.codeSizeCache.Add(codeHash, len(code))
@ -192,10 +191,11 @@ func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error
// code can't be found in the cache, then check the existence with **new** // code can't be found in the cache, then check the existence with **new**
// db scheme. // db scheme.
func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) { func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) {
if code := db.codeCache.Get(codeHash); len(code) > 0 { code, _ := db.codeCache.Get(codeHash)
if len(code) > 0 {
return code, nil return code, nil
} }
code := rawdb.ReadCodeWithPrefix(db.disk, codeHash) code = rawdb.ReadCodeWithPrefix(db.disk, codeHash)
if len(code) > 0 { if len(code) > 0 {
db.codeCache.Add(codeHash, code) db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code)) db.codeSizeCache.Add(codeHash, len(code))
@ -207,7 +207,7 @@ func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]b
// ContractCodeSize retrieves a particular contracts code's size. // ContractCodeSize retrieves a particular contracts code's size.
func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) { func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
if cached, ok := db.codeSizeCache.Get(codeHash); ok { if cached, ok := db.codeSizeCache.Get(codeHash); ok {
return cached.(int), nil return cached, nil
} }
code, err := db.ContractCode(addrHash, codeHash) code, err := db.ContractCode(addrHash, codeHash)
return len(code), err return len(code), err

View File

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -35,7 +36,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
lru "github.com/hashicorp/golang-lru"
) )
// Config represents the configuration of the filter system. // Config represents the configuration of the filter system.
@ -77,21 +77,16 @@ type Backend interface {
// FilterSystem holds resources shared by all filters. // FilterSystem holds resources shared by all filters.
type FilterSystem struct { type FilterSystem struct {
backend Backend backend Backend
logsCache *lru.Cache logsCache *lru.Cache[common.Hash, [][]*types.Log]
cfg *Config cfg *Config
} }
// NewFilterSystem creates a filter system. // NewFilterSystem creates a filter system.
func NewFilterSystem(backend Backend, config Config) *FilterSystem { func NewFilterSystem(backend Backend, config Config) *FilterSystem {
config = config.withDefaults() config = config.withDefaults()
cache, err := lru.New(config.LogCacheSize)
if err != nil {
panic(err)
}
return &FilterSystem{ return &FilterSystem{
backend: backend, backend: backend,
logsCache: cache, logsCache: lru.NewCache[common.Hash, [][]*types.Log](config.LogCacheSize),
cfg: &config, cfg: &config,
} }
} }
@ -100,7 +95,7 @@ func NewFilterSystem(backend Backend, config Config) *FilterSystem {
func (sys *FilterSystem) cachedGetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { func (sys *FilterSystem) cachedGetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) {
cached, ok := sys.logsCache.Get(blockHash) cached, ok := sys.logsCache.Get(blockHash)
if ok { if ok {
return cached.([][]*types.Log), nil return cached, nil
} }
logs, err := sys.backend.GetLogs(ctx, blockHash, number) logs, err := sys.backend.GetLogs(ctx, blockHash, number)

View File

@ -56,7 +56,12 @@ type blockFees struct {
err error err error
} }
// processedFees contains the results of a processed block and is also used for caching type cacheKey struct {
number uint64
percentiles string
}
// processedFees contains the results of a processed block.
type processedFees struct { type processedFees struct {
reward []*big.Int reward []*big.Int
baseFee, nextBaseFee *big.Int baseFee, nextBaseFee *big.Int
@ -270,13 +275,10 @@ func (oracle *Oracle) FeeHistory(ctx context.Context, blocks int, unresolvedLast
oracle.processBlock(fees, rewardPercentiles) oracle.processBlock(fees, rewardPercentiles)
results <- fees results <- fees
} else { } else {
cacheKey := struct { cacheKey := cacheKey{number: blockNumber, percentiles: string(percentileKey)}
number uint64
percentiles string
}{blockNumber, string(percentileKey)}
if p, ok := oracle.historyCache.Get(cacheKey); ok { if p, ok := oracle.historyCache.Get(cacheKey); ok {
fees.results = p.(processedFees) fees.results = p
results <- fees results <- fees
} else { } else {
if len(rewardPercentiles) != 0 { if len(rewardPercentiles) != 0 {

View File

@ -23,13 +23,13 @@ import (
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
lru "github.com/hashicorp/golang-lru"
) )
const sampleNumber = 3 // Number of transactions sampled in a block const sampleNumber = 3 // Number of transactions sampled in a block
@ -72,7 +72,8 @@ type Oracle struct {
checkBlocks, percentile int checkBlocks, percentile int
maxHeaderHistory, maxBlockHistory int maxHeaderHistory, maxBlockHistory int
historyCache *lru.Cache
historyCache *lru.Cache[cacheKey, processedFees]
} }
// NewOracle returns a new gasprice oracle which can recommend suitable // NewOracle returns a new gasprice oracle which can recommend suitable
@ -114,7 +115,7 @@ func NewOracle(backend OracleBackend, params Config) *Oracle {
log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory) log.Warn("Sanitizing invalid gasprice oracle max block history", "provided", params.MaxBlockHistory, "updated", maxBlockHistory)
} }
cache, _ := lru.New(2048) cache := lru.NewCache[cacheKey, processedFees](2048)
headEvent := make(chan core.ChainHeadEvent, 1) headEvent := make(chan core.ChainHeadEvent, 1)
backend.SubscribeChainHeadEvent(headEvent) backend.SubscribeChainHeadEvent(headEvent)
go func() { go func() {

View File

@ -22,10 +22,10 @@ import (
"encoding/binary" "encoding/binary"
"time" "time"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/hashicorp/golang-lru/simplelru"
) )
const handshakeTimeout = time.Second const handshakeTimeout = time.Second
@ -33,7 +33,7 @@ const handshakeTimeout = time.Second
// The SessionCache keeps negotiated encryption keys and // The SessionCache keeps negotiated encryption keys and
// state for in-progress handshakes in the Discovery v5 wire protocol. // state for in-progress handshakes in the Discovery v5 wire protocol.
type SessionCache struct { type SessionCache struct {
sessions *simplelru.LRU sessions lru.BasicLRU[sessionID, *session]
handshakes map[sessionID]*Whoareyou handshakes map[sessionID]*Whoareyou
clock mclock.Clock clock mclock.Clock
@ -62,12 +62,8 @@ func (s *session) keysFlipped() *session {
} }
func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache { func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
cache, err := simplelru.NewLRU(maxItems, nil)
if err != nil {
panic("can't create session cache")
}
return &SessionCache{ return &SessionCache{
sessions: cache, sessions: lru.NewBasicLRU[sessionID, *session](maxItems),
handshakes: make(map[sessionID]*Whoareyou), handshakes: make(map[sessionID]*Whoareyou),
clock: clock, clock: clock,
nonceGen: generateNonce, nonceGen: generateNonce,
@ -95,11 +91,8 @@ func (sc *SessionCache) nextNonce(s *session) (Nonce, error) {
// session returns the current session for the given node, if any. // session returns the current session for the given node, if any.
func (sc *SessionCache) session(id enode.ID, addr string) *session { func (sc *SessionCache) session(id enode.ID, addr string) *session {
item, ok := sc.sessions.Get(sessionID{id, addr}) item, _ := sc.sessions.Get(sessionID{id, addr})
if !ok { return item
return nil
}
return item.(*session)
} }
// readKey returns the current read key for the given node. // readKey returns the current read key for the given node.

View File

@ -27,12 +27,12 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/enr"
lru "github.com/hashicorp/golang-lru"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
"golang.org/x/time/rate" "golang.org/x/time/rate"
) )
@ -41,7 +41,7 @@ import (
type Client struct { type Client struct {
cfg Config cfg Config
clock mclock.Clock clock mclock.Clock
entries *lru.Cache entries *lru.Cache[string, entry]
ratelimit *rate.Limiter ratelimit *rate.Limiter
singleflight singleflight.Group singleflight singleflight.Group
} }
@ -96,14 +96,10 @@ func (cfg Config) withDefaults() Config {
// NewClient creates a client. // NewClient creates a client.
func NewClient(cfg Config) *Client { func NewClient(cfg Config) *Client {
cfg = cfg.withDefaults() cfg = cfg.withDefaults()
cache, err := lru.New(cfg.CacheLimit)
if err != nil {
panic(err)
}
rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10) rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10)
return &Client{ return &Client{
cfg: cfg, cfg: cfg,
entries: cache, entries: lru.NewCache[string, entry](cfg.CacheLimit),
clock: mclock.System{}, clock: mclock.System{},
ratelimit: rlimit, ratelimit: rlimit,
} }
@ -176,7 +172,7 @@ func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry,
} }
cacheKey := truncateHash(hash) cacheKey := truncateHash(hash)
if e, ok := c.entries.Get(cacheKey); ok { if e, ok := c.entries.Get(cacheKey); ok {
return e.(entry), nil return e, nil
} }
ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) { ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) {