We can use stock Ethereum versions without modifications.pull/250/head
parent
a258976bd1
commit
f599efd160
@ -1,242 +0,0 @@ |
|||||||
package db |
|
||||||
|
|
||||||
import ( |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/harmony-one/harmony/log" |
|
||||||
"github.com/syndtr/goleveldb/leveldb" |
|
||||||
"github.com/syndtr/goleveldb/leveldb/errors" |
|
||||||
"github.com/syndtr/goleveldb/leveldb/filter" |
|
||||||
"github.com/syndtr/goleveldb/leveldb/iterator" |
|
||||||
"github.com/syndtr/goleveldb/leveldb/opt" |
|
||||||
"github.com/syndtr/goleveldb/leveldb/util" |
|
||||||
) |
|
||||||
|
|
||||||
// Constants for db which can be used to customize later.
|
|
||||||
const ( |
|
||||||
writePauseWarningThrottler = 1 * time.Minute |
|
||||||
) |
|
||||||
|
|
||||||
// LDBDatabase is database based on leveldb.
|
|
||||||
type LDBDatabase struct { |
|
||||||
fn string // filename for reporting
|
|
||||||
db *leveldb.DB // LevelDB instance
|
|
||||||
|
|
||||||
quitLock sync.Mutex // Mutex protecting the quit channel access
|
|
||||||
quitChan chan chan error // Quit channel to stop the metrics collection before closing the database
|
|
||||||
|
|
||||||
log log.Logger // Contextual logger tracking the database path
|
|
||||||
} |
|
||||||
|
|
||||||
// NewLDBDatabase returns a LevelDB wrapped object.
|
|
||||||
func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error) { |
|
||||||
logger := log.New("database", file) |
|
||||||
|
|
||||||
// Ensure we have some minimal caching and file guarantees
|
|
||||||
if cache < 16 { |
|
||||||
cache = 16 |
|
||||||
} |
|
||||||
if handles < 16 { |
|
||||||
handles = 16 |
|
||||||
} |
|
||||||
logger.Info("Allocated cache and file handles", "cache", cache, "handles", handles) |
|
||||||
|
|
||||||
// Open the db and recover any potential corruptions
|
|
||||||
db, err := leveldb.OpenFile(file, &opt.Options{ |
|
||||||
OpenFilesCacheCapacity: handles, |
|
||||||
BlockCacheCapacity: cache / 2 * opt.MiB, |
|
||||||
WriteBuffer: cache / 4 * opt.MiB, // Two of these are used internally
|
|
||||||
Filter: filter.NewBloomFilter(10), |
|
||||||
}) |
|
||||||
if _, corrupted := err.(*errors.ErrCorrupted); corrupted { |
|
||||||
db, err = leveldb.RecoverFile(file, nil) |
|
||||||
} |
|
||||||
// (Re)check for errors and abort if opening of the db failed
|
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return &LDBDatabase{ |
|
||||||
fn: file, |
|
||||||
db: db, |
|
||||||
log: logger, |
|
||||||
}, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Path returns the path to the database directory.
|
|
||||||
func (db *LDBDatabase) Path() string { |
|
||||||
return db.fn |
|
||||||
} |
|
||||||
|
|
||||||
// Put puts the given key / value to the queue
|
|
||||||
func (db *LDBDatabase) Put(key []byte, value []byte) error { |
|
||||||
return db.db.Put(key, value, nil) |
|
||||||
} |
|
||||||
|
|
||||||
// Has is used to check if the given key is included into the database.
|
|
||||||
func (db *LDBDatabase) Has(key []byte) (bool, error) { |
|
||||||
return db.db.Has(key, nil) |
|
||||||
} |
|
||||||
|
|
||||||
// Get returns the given key if it's present.
|
|
||||||
func (db *LDBDatabase) Get(key []byte) ([]byte, error) { |
|
||||||
dat, err := db.db.Get(key, nil) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return dat, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Delete deletes the key from the queue and database
|
|
||||||
func (db *LDBDatabase) Delete(key []byte) error { |
|
||||||
return db.db.Delete(key, nil) |
|
||||||
} |
|
||||||
|
|
||||||
// NewIterator returns the current iterator of the db.
|
|
||||||
func (db *LDBDatabase) NewIterator() iterator.Iterator { |
|
||||||
return db.db.NewIterator(nil, nil) |
|
||||||
} |
|
||||||
|
|
||||||
// NewIteratorWithPrefix returns a iterator to iterate over subset of database content with a particular prefix.
|
|
||||||
func (db *LDBDatabase) NewIteratorWithPrefix(prefix []byte) iterator.Iterator { |
|
||||||
return db.db.NewIterator(util.BytesPrefix(prefix), nil) |
|
||||||
} |
|
||||||
|
|
||||||
// Close closes the database.
|
|
||||||
func (db *LDBDatabase) Close() { |
|
||||||
// Stop the metrics collection to avoid internal database races
|
|
||||||
db.quitLock.Lock() |
|
||||||
defer db.quitLock.Unlock() |
|
||||||
|
|
||||||
if db.quitChan != nil { |
|
||||||
errc := make(chan error) |
|
||||||
db.quitChan <- errc |
|
||||||
if err := <-errc; err != nil { |
|
||||||
db.log.Error("Metrics collection failed", "err", err) |
|
||||||
} |
|
||||||
db.quitChan = nil |
|
||||||
} |
|
||||||
err := db.db.Close() |
|
||||||
if err == nil { |
|
||||||
db.log.Info("Database closed") |
|
||||||
} else { |
|
||||||
db.log.Error("Failed to close database", "err", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// LDB returns the pointer to leveldb on which the LDBDatabase is built.
|
|
||||||
func (db *LDBDatabase) LDB() *leveldb.DB { |
|
||||||
return db.db |
|
||||||
} |
|
||||||
|
|
||||||
/* TODO(minhdoan): Might add meter func from ethereum-go repo |
|
||||||
*/ |
|
||||||
|
|
||||||
// NewBatch returns Batch interface for a series of leveldb transactions.
|
|
||||||
func (db *LDBDatabase) NewBatch() Batch { |
|
||||||
return &ldbBatch{db: db.db, b: new(leveldb.Batch)} |
|
||||||
} |
|
||||||
|
|
||||||
type ldbBatch struct { |
|
||||||
db *leveldb.DB |
|
||||||
b *leveldb.Batch |
|
||||||
size int |
|
||||||
} |
|
||||||
|
|
||||||
// Put is used to put key, value into the batch of transactions.
|
|
||||||
func (b *ldbBatch) Put(key, value []byte) error { |
|
||||||
b.b.Put(key, value) |
|
||||||
b.size += len(value) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Delete is used to delete the item associated with the given key as a part of the batch.
|
|
||||||
func (b *ldbBatch) Delete(key []byte) error { |
|
||||||
b.b.Delete(key) |
|
||||||
b.size++ |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Write writes the patch of transactions.
|
|
||||||
func (b *ldbBatch) Write() error { |
|
||||||
return b.db.Write(b.b, nil) |
|
||||||
} |
|
||||||
|
|
||||||
// ValueSize returns the size of the patch.
|
|
||||||
func (b *ldbBatch) ValueSize() int { |
|
||||||
return b.size |
|
||||||
} |
|
||||||
|
|
||||||
// Reset resets the batch.
|
|
||||||
func (b *ldbBatch) Reset() { |
|
||||||
b.b.Reset() |
|
||||||
b.size = 0 |
|
||||||
} |
|
||||||
|
|
||||||
type table struct { |
|
||||||
db Database |
|
||||||
prefix string |
|
||||||
} |
|
||||||
|
|
||||||
// NewTable returns a Database object that prefixes all keys with a given
|
|
||||||
// string.
|
|
||||||
func NewTable(db Database, prefix string) Database { |
|
||||||
return &table{ |
|
||||||
db: db, |
|
||||||
prefix: prefix, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (dt *table) Put(key []byte, value []byte) error { |
|
||||||
return dt.db.Put(append([]byte(dt.prefix), key...), value) |
|
||||||
} |
|
||||||
|
|
||||||
func (dt *table) Has(key []byte) (bool, error) { |
|
||||||
return dt.db.Has(append([]byte(dt.prefix), key...)) |
|
||||||
} |
|
||||||
|
|
||||||
func (dt *table) Get(key []byte) ([]byte, error) { |
|
||||||
return dt.db.Get(append([]byte(dt.prefix), key...)) |
|
||||||
} |
|
||||||
|
|
||||||
func (dt *table) Delete(key []byte) error { |
|
||||||
return dt.db.Delete(append([]byte(dt.prefix), key...)) |
|
||||||
} |
|
||||||
|
|
||||||
func (dt *table) Close() { |
|
||||||
// Do nothing; don't close the underlying DB.
|
|
||||||
} |
|
||||||
|
|
||||||
type tableBatch struct { |
|
||||||
batch Batch |
|
||||||
prefix string |
|
||||||
} |
|
||||||
|
|
||||||
// NewTableBatch returns a Batch object which prefixes all keys with a given string.
|
|
||||||
func NewTableBatch(db Database, prefix string) Batch { |
|
||||||
return &tableBatch{db.NewBatch(), prefix} |
|
||||||
} |
|
||||||
|
|
||||||
func (dt *table) NewBatch() Batch { |
|
||||||
return &tableBatch{dt.db.NewBatch(), dt.prefix} |
|
||||||
} |
|
||||||
|
|
||||||
func (tb *tableBatch) Put(key, value []byte) error { |
|
||||||
return tb.batch.Put(append([]byte(tb.prefix), key...), value) |
|
||||||
} |
|
||||||
|
|
||||||
func (tb *tableBatch) Delete(key []byte) error { |
|
||||||
return tb.batch.Delete(append([]byte(tb.prefix), key...)) |
|
||||||
} |
|
||||||
|
|
||||||
func (tb *tableBatch) Write() error { |
|
||||||
return tb.batch.Write() |
|
||||||
} |
|
||||||
|
|
||||||
func (tb *tableBatch) ValueSize() int { |
|
||||||
return tb.batch.ValueSize() |
|
||||||
} |
|
||||||
|
|
||||||
func (tb *tableBatch) Reset() { |
|
||||||
tb.batch.Reset() |
|
||||||
} |
|
@ -1,194 +0,0 @@ |
|||||||
package db |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"fmt" |
|
||||||
"io/ioutil" |
|
||||||
"os" |
|
||||||
"strconv" |
|
||||||
"sync" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func newTestLDB() (*LDBDatabase, func()) { |
|
||||||
dirname, err := ioutil.TempDir(os.TempDir(), "db_test_") |
|
||||||
if err != nil { |
|
||||||
panic("failed to create test file: " + err.Error()) |
|
||||||
} |
|
||||||
db, err := NewLDBDatabase(dirname, 0, 0) |
|
||||||
if err != nil { |
|
||||||
panic("failed to create test database: " + err.Error()) |
|
||||||
} |
|
||||||
|
|
||||||
return db, func() { |
|
||||||
db.Close() |
|
||||||
os.RemoveAll(dirname) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var testValues = []string{"", "a", "1251", "\x00123\x00"} |
|
||||||
|
|
||||||
func TestLDB_PutGet(t *testing.T) { |
|
||||||
db, remove := newTestLDB() |
|
||||||
defer remove() |
|
||||||
testPutGet(db, t) |
|
||||||
} |
|
||||||
|
|
||||||
func TestMemoryDB_PutGet(t *testing.T) { |
|
||||||
testPutGet(NewMemDatabase(), t) |
|
||||||
} |
|
||||||
|
|
||||||
func testPutGet(db Database, t *testing.T) { |
|
||||||
t.Parallel() |
|
||||||
|
|
||||||
for _, k := range testValues { |
|
||||||
err := db.Put([]byte(k), nil) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("put failed: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for _, k := range testValues { |
|
||||||
data, err := db.Get([]byte(k)) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("get failed: %v", err) |
|
||||||
} |
|
||||||
if len(data) != 0 { |
|
||||||
t.Fatalf("get returned wrong result, got %q expected nil", string(data)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
_, err := db.Get([]byte("non-exist-key")) |
|
||||||
if err == nil { |
|
||||||
t.Fatalf("expect to return a not found error") |
|
||||||
} |
|
||||||
|
|
||||||
for _, v := range testValues { |
|
||||||
err := db.Put([]byte(v), []byte(v)) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("put failed: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for _, v := range testValues { |
|
||||||
data, err := db.Get([]byte(v)) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("get failed: %v", err) |
|
||||||
} |
|
||||||
if !bytes.Equal(data, []byte(v)) { |
|
||||||
t.Fatalf("get returned wrong result, got %q expected %q", string(data), v) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for _, v := range testValues { |
|
||||||
err := db.Put([]byte(v), []byte("?")) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("put override failed: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for _, v := range testValues { |
|
||||||
data, err := db.Get([]byte(v)) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("get failed: %v", err) |
|
||||||
} |
|
||||||
if !bytes.Equal(data, []byte("?")) { |
|
||||||
t.Fatalf("get returned wrong result, got %q expected ?", string(data)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for _, v := range testValues { |
|
||||||
orig, err := db.Get([]byte(v)) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("get failed: %v", err) |
|
||||||
} |
|
||||||
orig[0] = byte(0xff) |
|
||||||
data, err := db.Get([]byte(v)) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("get failed: %v", err) |
|
||||||
} |
|
||||||
if !bytes.Equal(data, []byte("?")) { |
|
||||||
t.Fatalf("get returned wrong result, got %q expected ?", string(data)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for _, v := range testValues { |
|
||||||
err := db.Delete([]byte(v)) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("delete %q failed: %v", v, err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for _, v := range testValues { |
|
||||||
_, err := db.Get([]byte(v)) |
|
||||||
if err == nil { |
|
||||||
t.Fatalf("got deleted value %q", v) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestLDB_ParallelPutGet(t *testing.T) { |
|
||||||
db, remove := newTestLDB() |
|
||||||
defer remove() |
|
||||||
testParallelPutGet(db, t) |
|
||||||
} |
|
||||||
|
|
||||||
func TestMemoryDB_ParallelPutGet(t *testing.T) { |
|
||||||
testParallelPutGet(NewMemDatabase(), t) |
|
||||||
} |
|
||||||
|
|
||||||
func testParallelPutGet(db Database, t *testing.T) { |
|
||||||
const n = 8 |
|
||||||
var pending sync.WaitGroup |
|
||||||
|
|
||||||
pending.Add(n) |
|
||||||
for i := 0; i < n; i++ { |
|
||||||
go func(key string) { |
|
||||||
defer pending.Done() |
|
||||||
err := db.Put([]byte(key), []byte("v"+key)) |
|
||||||
if err != nil { |
|
||||||
panic("put failed: " + err.Error()) |
|
||||||
} |
|
||||||
}(strconv.Itoa(i)) |
|
||||||
} |
|
||||||
pending.Wait() |
|
||||||
|
|
||||||
pending.Add(n) |
|
||||||
for i := 0; i < n; i++ { |
|
||||||
go func(key string) { |
|
||||||
defer pending.Done() |
|
||||||
data, err := db.Get([]byte(key)) |
|
||||||
if err != nil { |
|
||||||
panic("get failed: " + err.Error()) |
|
||||||
} |
|
||||||
if !bytes.Equal(data, []byte("v"+key)) { |
|
||||||
panic(fmt.Sprintf("get failed, got %q expected %q", []byte(data), []byte("v"+key))) |
|
||||||
} |
|
||||||
}(strconv.Itoa(i)) |
|
||||||
} |
|
||||||
pending.Wait() |
|
||||||
|
|
||||||
pending.Add(n) |
|
||||||
for i := 0; i < n; i++ { |
|
||||||
go func(key string) { |
|
||||||
defer pending.Done() |
|
||||||
err := db.Delete([]byte(key)) |
|
||||||
if err != nil { |
|
||||||
panic("delete failed: " + err.Error()) |
|
||||||
} |
|
||||||
}(strconv.Itoa(i)) |
|
||||||
} |
|
||||||
pending.Wait() |
|
||||||
|
|
||||||
pending.Add(n) |
|
||||||
for i := 0; i < n; i++ { |
|
||||||
go func(key string) { |
|
||||||
defer pending.Done() |
|
||||||
_, err := db.Get([]byte(key)) |
|
||||||
if err == nil { |
|
||||||
panic("get succeeded") |
|
||||||
} |
|
||||||
}(strconv.Itoa(i)) |
|
||||||
} |
|
||||||
pending.Wait() |
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
package db |
|
||||||
|
|
||||||
// IdealBatchSize is the max size of batch transactions.
|
|
||||||
// The value was determined empirically.
|
|
||||||
const IdealBatchSize = 100 * 1024 |
|
||||||
|
|
||||||
// Putter wraps the database write operation supported by both batches and regular databases.
|
|
||||||
type Putter interface { |
|
||||||
Put(key []byte, value []byte) error |
|
||||||
} |
|
||||||
|
|
||||||
// Deleter wraps the database delete operation supported by both batches and regular databases.
|
|
||||||
type Deleter interface { |
|
||||||
Delete(key []byte) error |
|
||||||
} |
|
||||||
|
|
||||||
// Database wraps all database operations. All methods are safe for concurrent use.
|
|
||||||
type Database interface { |
|
||||||
Putter |
|
||||||
Deleter |
|
||||||
Get(key []byte) ([]byte, error) |
|
||||||
Has(key []byte) (bool, error) |
|
||||||
Close() |
|
||||||
NewBatch() Batch |
|
||||||
} |
|
||||||
|
|
||||||
// Batch is a write-only database that commits changes to its host database
|
|
||||||
// when Write is called. Batch cannot be used concurrently.
|
|
||||||
type Batch interface { |
|
||||||
Putter |
|
||||||
Deleter |
|
||||||
ValueSize() int // amount of data in the batch
|
|
||||||
Write() error |
|
||||||
// Reset resets the batch for reuse
|
|
||||||
Reset() |
|
||||||
} |
|
@ -1,135 +0,0 @@ |
|||||||
package db |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"sync" |
|
||||||
|
|
||||||
"github.com/harmony-one/harmony/internal/utils" |
|
||||||
) |
|
||||||
|
|
||||||
// MemDatabase is the test memory database. It won't be used for any production.
|
|
||||||
type MemDatabase struct { |
|
||||||
db map[string][]byte |
|
||||||
lock sync.RWMutex |
|
||||||
} |
|
||||||
|
|
||||||
// NewMemDatabase returns a pointer of the new creation of MemDatabase.
|
|
||||||
func NewMemDatabase() *MemDatabase { |
|
||||||
return &MemDatabase{ |
|
||||||
db: make(map[string][]byte), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// NewMemDatabaseWithCap returns a pointer of the new creation of MemDatabase with the given size.
|
|
||||||
func NewMemDatabaseWithCap(size int) *MemDatabase { |
|
||||||
return &MemDatabase{ |
|
||||||
db: make(map[string][]byte, size), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Put puts (key, value) item into MemDatabase.
|
|
||||||
func (db *MemDatabase) Put(key []byte, value []byte) error { |
|
||||||
db.lock.Lock() |
|
||||||
defer db.lock.Unlock() |
|
||||||
|
|
||||||
db.db[string(key)] = utils.CopyBytes(value) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Has checks if the key is included into MemDatabase.
|
|
||||||
func (db *MemDatabase) Has(key []byte) (bool, error) { |
|
||||||
db.lock.RLock() |
|
||||||
defer db.lock.RUnlock() |
|
||||||
|
|
||||||
_, ok := db.db[string(key)] |
|
||||||
return ok, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Get gets value of the given key.
|
|
||||||
func (db *MemDatabase) Get(key []byte) ([]byte, error) { |
|
||||||
db.lock.RLock() |
|
||||||
defer db.lock.RUnlock() |
|
||||||
|
|
||||||
if entry, ok := db.db[string(key)]; ok { |
|
||||||
return utils.CopyBytes(entry), nil |
|
||||||
} |
|
||||||
return nil, errors.New("not found") |
|
||||||
} |
|
||||||
|
|
||||||
// Keys returns all keys of the given MemDatabase.
|
|
||||||
func (db *MemDatabase) Keys() [][]byte { |
|
||||||
db.lock.RLock() |
|
||||||
defer db.lock.RUnlock() |
|
||||||
|
|
||||||
keys := [][]byte{} |
|
||||||
for key := range db.db { |
|
||||||
keys = append(keys, []byte(key)) |
|
||||||
} |
|
||||||
return keys |
|
||||||
} |
|
||||||
|
|
||||||
// Delete deletes the given key.
|
|
||||||
func (db *MemDatabase) Delete(key []byte) error { |
|
||||||
db.lock.Lock() |
|
||||||
defer db.lock.Unlock() |
|
||||||
|
|
||||||
delete(db.db, string(key)) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Close closes the given db.
|
|
||||||
func (db *MemDatabase) Close() {} |
|
||||||
|
|
||||||
// NewBatch returns a batch of MemDatabase transactions.
|
|
||||||
func (db *MemDatabase) NewBatch() Batch { |
|
||||||
return &memBatch{db: db} |
|
||||||
} |
|
||||||
|
|
||||||
// Len returns the length of the given db.
|
|
||||||
func (db *MemDatabase) Len() int { return len(db.db) } |
|
||||||
|
|
||||||
type kv struct { |
|
||||||
k, v []byte |
|
||||||
del bool |
|
||||||
} |
|
||||||
|
|
||||||
type memBatch struct { |
|
||||||
db *MemDatabase |
|
||||||
writes []kv |
|
||||||
size int |
|
||||||
} |
|
||||||
|
|
||||||
func (b *memBatch) Put(key, value []byte) error { |
|
||||||
b.writes = append(b.writes, kv{utils.CopyBytes(key), utils.CopyBytes(value), false}) |
|
||||||
b.size += len(value) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (b *memBatch) Delete(key []byte) error { |
|
||||||
b.writes = append(b.writes, kv{utils.CopyBytes(key), nil, true}) |
|
||||||
b.size++ |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (b *memBatch) Write() error { |
|
||||||
b.db.lock.Lock() |
|
||||||
defer b.db.lock.Unlock() |
|
||||||
|
|
||||||
for _, kv := range b.writes { |
|
||||||
if kv.del { |
|
||||||
delete(b.db.db, string(kv.k)) |
|
||||||
continue |
|
||||||
} |
|
||||||
b.db.db[string(kv.k)] = kv.v |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (b *memBatch) ValueSize() int { |
|
||||||
return b.size |
|
||||||
} |
|
||||||
|
|
||||||
func (b *memBatch) Reset() { |
|
||||||
b.writes = b.writes[:0] |
|
||||||
b.size = 0 |
|
||||||
} |
|
@ -1,782 +0,0 @@ |
|||||||
// Copyright 2018 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/log" |
|
||||||
"github.com/ethereum/go-ethereum/metrics" |
|
||||||
"github.com/ethereum/go-ethereum/rlp" |
|
||||||
hdb "github.com/harmony-one/harmony/internal/db" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
memcacheFlushTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/flush/time", nil) |
|
||||||
memcacheFlushNodesMeter = metrics.NewRegisteredMeter("trie/memcache/flush/nodes", nil) |
|
||||||
memcacheFlushSizeMeter = metrics.NewRegisteredMeter("trie/memcache/flush/size", nil) |
|
||||||
|
|
||||||
memcacheGCTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/gc/time", nil) |
|
||||||
memcacheGCNodesMeter = metrics.NewRegisteredMeter("trie/memcache/gc/nodes", nil) |
|
||||||
memcacheGCSizeMeter = metrics.NewRegisteredMeter("trie/memcache/gc/size", nil) |
|
||||||
|
|
||||||
memcacheCommitTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/commit/time", nil) |
|
||||||
memcacheCommitNodesMeter = metrics.NewRegisteredMeter("trie/memcache/commit/nodes", nil) |
|
||||||
memcacheCommitSizeMeter = metrics.NewRegisteredMeter("trie/memcache/commit/size", nil) |
|
||||||
) |
|
||||||
|
|
||||||
// secureKeyPrefix is the database key prefix used to store trie node preimages.
|
|
||||||
var secureKeyPrefix = []byte("secure-key-") |
|
||||||
|
|
||||||
// secureKeyLength is the length of the above prefix + 32byte hash.
|
|
||||||
const secureKeyLength = 11 + 32 |
|
||||||
|
|
||||||
// DatabaseReader wraps the Get and Has method of a backing store for the trie.
|
|
||||||
type DatabaseReader interface { |
|
||||||
// Get retrieves the value associated with key from the database.
|
|
||||||
Get(key []byte) (value []byte, err error) |
|
||||||
|
|
||||||
// Has retrieves whether a key is present in the database.
|
|
||||||
Has(key []byte) (bool, error) |
|
||||||
} |
|
||||||
|
|
||||||
// Database is an intermediate write layer between the trie data structures and
|
|
||||||
// the disk database. The aim is to accumulate trie writes in-memory and only
|
|
||||||
// periodically flush a couple tries to disk, garbage collecting the remainder.
|
|
||||||
type Database struct { |
|
||||||
diskdb hdb.Database // Persistent storage for matured trie nodes
|
|
||||||
|
|
||||||
nodes map[common.Hash]*cachedNode // Data and references relationships of a node
|
|
||||||
oldest common.Hash // Oldest tracked node, flush-list head
|
|
||||||
newest common.Hash // Newest tracked node, flush-list tail
|
|
||||||
|
|
||||||
preimages map[common.Hash][]byte // Preimages of nodes from the secure trie
|
|
||||||
seckeybuf [secureKeyLength]byte // Ephemeral buffer for calculating preimage keys
|
|
||||||
|
|
||||||
gctime time.Duration // Time spent on garbage collection since last commit
|
|
||||||
gcnodes uint64 // Nodes garbage collected since last commit
|
|
||||||
gcsize common.StorageSize // Data storage garbage collected since last commit
|
|
||||||
|
|
||||||
flushtime time.Duration // Time spent on data flushing since last commit
|
|
||||||
flushnodes uint64 // Nodes flushed since last commit
|
|
||||||
flushsize common.StorageSize // Data storage flushed since last commit
|
|
||||||
|
|
||||||
nodesSize common.StorageSize // Storage size of the nodes cache (exc. flushlist)
|
|
||||||
preimagesSize common.StorageSize // Storage size of the preimages cache
|
|
||||||
|
|
||||||
lock sync.RWMutex |
|
||||||
} |
|
||||||
|
|
||||||
// rawNode is a simple binary blob used to differentiate between collapsed trie
|
|
||||||
// nodes and already encoded RLP binary blobs (while at the same time store them
|
|
||||||
// in the same cache fields).
|
|
||||||
type rawNode []byte |
|
||||||
|
|
||||||
func (n rawNode) canUnload(uint16, uint16) bool { panic("this should never end up in a live trie") } |
|
||||||
func (n rawNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") } |
|
||||||
func (n rawNode) fstring(ind string) string { panic("this should never end up in a live trie") } |
|
||||||
|
|
||||||
// rawFullNode represents only the useful data content of a full node, with the
|
|
||||||
// caches and flags stripped out to minimize its data storage. This type honors
|
|
||||||
// the same RLP encoding as the original parent.
|
|
||||||
type rawFullNode [17]node |
|
||||||
|
|
||||||
func (n rawFullNode) canUnload(uint16, uint16) bool { panic("this should never end up in a live trie") } |
|
||||||
func (n rawFullNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") } |
|
||||||
func (n rawFullNode) fstring(ind string) string { panic("this should never end up in a live trie") } |
|
||||||
|
|
||||||
func (n rawFullNode) EncodeRLP(w io.Writer) error { |
|
||||||
var nodes [17]node |
|
||||||
|
|
||||||
for i, child := range n { |
|
||||||
if child != nil { |
|
||||||
nodes[i] = child |
|
||||||
} else { |
|
||||||
nodes[i] = nilValueNode |
|
||||||
} |
|
||||||
} |
|
||||||
return rlp.Encode(w, nodes) |
|
||||||
} |
|
||||||
|
|
||||||
// rawShortNode represents only the useful data content of a short node, with the
|
|
||||||
// caches and flags stripped out to minimize its data storage. This type honors
|
|
||||||
// the same RLP encoding as the original parent.
|
|
||||||
type rawShortNode struct { |
|
||||||
Key []byte |
|
||||||
Val node |
|
||||||
} |
|
||||||
|
|
||||||
func (n rawShortNode) canUnload(uint16, uint16) bool { panic("this should never end up in a live trie") } |
|
||||||
func (n rawShortNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") } |
|
||||||
func (n rawShortNode) fstring(ind string) string { panic("this should never end up in a live trie") } |
|
||||||
|
|
||||||
// cachedNode is all the information we know about a single cached node in the
|
|
||||||
// memory database write layer.
|
|
||||||
type cachedNode struct { |
|
||||||
node node // Cached collapsed trie node, or raw rlp data
|
|
||||||
size uint16 // Byte size of the useful cached data
|
|
||||||
|
|
||||||
parents uint16 // Number of live nodes referencing this one
|
|
||||||
children map[common.Hash]uint16 // External children referenced by this node
|
|
||||||
|
|
||||||
flushPrev common.Hash // Previous node in the flush-list
|
|
||||||
flushNext common.Hash // Next node in the flush-list
|
|
||||||
} |
|
||||||
|
|
||||||
// rlp returns the raw rlp encoded blob of the cached node, either directly from
|
|
||||||
// the cache, or by regenerating it from the collapsed node.
|
|
||||||
func (n *cachedNode) rlp() []byte { |
|
||||||
if node, ok := n.node.(rawNode); ok { |
|
||||||
return node |
|
||||||
} |
|
||||||
blob, err := rlp.EncodeToBytes(n.node) |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
return blob |
|
||||||
} |
|
||||||
|
|
||||||
// obj returns the decoded and expanded trie node, either directly from the cache,
|
|
||||||
// or by regenerating it from the rlp encoded blob.
|
|
||||||
func (n *cachedNode) obj(hash common.Hash, cachegen uint16) node { |
|
||||||
if node, ok := n.node.(rawNode); ok { |
|
||||||
return mustDecodeNode(hash[:], node, cachegen) |
|
||||||
} |
|
||||||
return expandNode(hash[:], n.node, cachegen) |
|
||||||
} |
|
||||||
|
|
||||||
// childs returns all the tracked children of this node, both the implicit ones
|
|
||||||
// from inside the node as well as the explicit ones from outside the node.
|
|
||||||
func (n *cachedNode) childs() []common.Hash { |
|
||||||
children := make([]common.Hash, 0, 16) |
|
||||||
for child := range n.children { |
|
||||||
children = append(children, child) |
|
||||||
} |
|
||||||
if _, ok := n.node.(rawNode); !ok { |
|
||||||
gatherChildren(n.node, &children) |
|
||||||
} |
|
||||||
return children |
|
||||||
} |
|
||||||
|
|
||||||
// gatherChildren traverses the node hierarchy of a collapsed storage node and
|
|
||||||
// retrieves all the hashnode children.
|
|
||||||
func gatherChildren(n node, children *[]common.Hash) { |
|
||||||
switch n := n.(type) { |
|
||||||
case *rawShortNode: |
|
||||||
gatherChildren(n.Val, children) |
|
||||||
|
|
||||||
case rawFullNode: |
|
||||||
for i := 0; i < 16; i++ { |
|
||||||
gatherChildren(n[i], children) |
|
||||||
} |
|
||||||
case hashNode: |
|
||||||
*children = append(*children, common.BytesToHash(n)) |
|
||||||
|
|
||||||
case valueNode, nil: |
|
||||||
|
|
||||||
default: |
|
||||||
panic(fmt.Sprintf("unknown node type: %T", n)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// simplifyNode traverses the hierarchy of an expanded memory node and discards
|
|
||||||
// all the internal caches, returning a node that only contains the raw data.
|
|
||||||
func simplifyNode(n node) node { |
|
||||||
switch n := n.(type) { |
|
||||||
case *shortNode: |
|
||||||
// Short nodes discard the flags and cascade
|
|
||||||
return &rawShortNode{Key: n.Key, Val: simplifyNode(n.Val)} |
|
||||||
|
|
||||||
case *fullNode: |
|
||||||
// Full nodes discard the flags and cascade
|
|
||||||
node := rawFullNode(n.Children) |
|
||||||
for i := 0; i < len(node); i++ { |
|
||||||
if node[i] != nil { |
|
||||||
node[i] = simplifyNode(node[i]) |
|
||||||
} |
|
||||||
} |
|
||||||
return node |
|
||||||
|
|
||||||
case valueNode, hashNode, rawNode: |
|
||||||
return n |
|
||||||
|
|
||||||
default: |
|
||||||
panic(fmt.Sprintf("unknown node type: %T", n)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// expandNode traverses the node hierarchy of a collapsed storage node and converts
|
|
||||||
// all fields and keys into expanded memory form.
|
|
||||||
func expandNode(hash hashNode, n node, cachegen uint16) node { |
|
||||||
switch n := n.(type) { |
|
||||||
case *rawShortNode: |
|
||||||
// Short nodes need key and child expansion
|
|
||||||
return &shortNode{ |
|
||||||
Key: compactToHex(n.Key), |
|
||||||
Val: expandNode(nil, n.Val, cachegen), |
|
||||||
flags: nodeFlag{ |
|
||||||
hash: hash, |
|
||||||
gen: cachegen, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
case rawFullNode: |
|
||||||
// Full nodes need child expansion
|
|
||||||
node := &fullNode{ |
|
||||||
flags: nodeFlag{ |
|
||||||
hash: hash, |
|
||||||
gen: cachegen, |
|
||||||
}, |
|
||||||
} |
|
||||||
for i := 0; i < len(node.Children); i++ { |
|
||||||
if n[i] != nil { |
|
||||||
node.Children[i] = expandNode(nil, n[i], cachegen) |
|
||||||
} |
|
||||||
} |
|
||||||
return node |
|
||||||
|
|
||||||
case valueNode, hashNode: |
|
||||||
return n |
|
||||||
|
|
||||||
default: |
|
||||||
panic(fmt.Sprintf("unknown node type: %T", n)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// NewDatabase creates a new trie database to store ephemeral trie content before
|
|
||||||
// its written out to disk or garbage collected.
|
|
||||||
func NewDatabase(diskdb hdb.Database) *Database { |
|
||||||
return &Database{ |
|
||||||
diskdb: diskdb, |
|
||||||
nodes: map[common.Hash]*cachedNode{{}: {}}, |
|
||||||
preimages: make(map[common.Hash][]byte), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// DiskDB retrieves the persistent storage backing the trie database.
|
|
||||||
func (db *Database) DiskDB() DatabaseReader { |
|
||||||
return db.diskdb |
|
||||||
} |
|
||||||
|
|
||||||
// InsertBlob writes a new reference tracked blob to the memory database if it's
|
|
||||||
// yet unknown. This method should only be used for non-trie nodes that require
|
|
||||||
// reference counting, since trie nodes are garbage collected directly through
|
|
||||||
// their embedded children.
|
|
||||||
func (db *Database) InsertBlob(hash common.Hash, blob []byte) { |
|
||||||
db.lock.Lock() |
|
||||||
defer db.lock.Unlock() |
|
||||||
|
|
||||||
db.insert(hash, blob, rawNode(blob)) |
|
||||||
} |
|
||||||
|
|
||||||
// insert inserts a collapsed trie node into the memory database. This method is
|
|
||||||
// a more generic version of InsertBlob, supporting both raw blob insertions as
|
|
||||||
// well ex trie node insertions. The blob must always be specified to allow proper
|
|
||||||
// size tracking.
|
|
||||||
func (db *Database) insert(hash common.Hash, blob []byte, node node) { |
|
||||||
// If the node's already cached, skip
|
|
||||||
if _, ok := db.nodes[hash]; ok { |
|
||||||
return |
|
||||||
} |
|
||||||
// Create the cached entry for this node
|
|
||||||
entry := &cachedNode{ |
|
||||||
node: simplifyNode(node), |
|
||||||
size: uint16(len(blob)), |
|
||||||
flushPrev: db.newest, |
|
||||||
} |
|
||||||
for _, child := range entry.childs() { |
|
||||||
if c := db.nodes[child]; c != nil { |
|
||||||
c.parents++ |
|
||||||
} |
|
||||||
} |
|
||||||
db.nodes[hash] = entry |
|
||||||
|
|
||||||
// Update the flush-list endpoints
|
|
||||||
if db.oldest == (common.Hash{}) { |
|
||||||
db.oldest, db.newest = hash, hash |
|
||||||
} else { |
|
||||||
db.nodes[db.newest].flushNext, db.newest = hash, hash |
|
||||||
} |
|
||||||
db.nodesSize += common.StorageSize(common.HashLength + entry.size) |
|
||||||
} |
|
||||||
|
|
||||||
// insertPreimage writes a new trie node pre-image to the memory database if it's
|
|
||||||
// yet unknown. The method will make a copy of the slice.
|
|
||||||
//
|
|
||||||
// Note, this method assumes that the database's lock is held!
|
|
||||||
func (db *Database) insertPreimage(hash common.Hash, preimage []byte) { |
|
||||||
if _, ok := db.preimages[hash]; ok { |
|
||||||
return |
|
||||||
} |
|
||||||
db.preimages[hash] = common.CopyBytes(preimage) |
|
||||||
db.preimagesSize += common.StorageSize(common.HashLength + len(preimage)) |
|
||||||
} |
|
||||||
|
|
||||||
// node retrieves a cached trie node from memory, or returns nil if none can be
|
|
||||||
// found in the memory cache.
|
|
||||||
func (db *Database) node(hash common.Hash, cachegen uint16) node { |
|
||||||
// Retrieve the node from cache if available
|
|
||||||
db.lock.RLock() |
|
||||||
node := db.nodes[hash] |
|
||||||
db.lock.RUnlock() |
|
||||||
|
|
||||||
if node != nil { |
|
||||||
return node.obj(hash, cachegen) |
|
||||||
} |
|
||||||
// Content unavailable in memory, attempt to retrieve from disk
|
|
||||||
enc, err := db.diskdb.Get(hash[:]) |
|
||||||
if err != nil || enc == nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
return mustDecodeNode(hash[:], enc, cachegen) |
|
||||||
} |
|
||||||
|
|
||||||
// Node retrieves an encoded cached trie node from memory. If it cannot be found
|
|
||||||
// cached, the method queries the persistent database for the content.
|
|
||||||
func (db *Database) Node(hash common.Hash) ([]byte, error) { |
|
||||||
// Retrieve the node from cache if available
|
|
||||||
db.lock.RLock() |
|
||||||
node := db.nodes[hash] |
|
||||||
db.lock.RUnlock() |
|
||||||
|
|
||||||
if node != nil { |
|
||||||
return node.rlp(), nil |
|
||||||
} |
|
||||||
// Content unavailable in memory, attempt to retrieve from disk
|
|
||||||
return db.diskdb.Get(hash[:]) |
|
||||||
} |
|
||||||
|
|
||||||
// preimage retrieves a cached trie node pre-image from memory. If it cannot be
|
|
||||||
// found cached, the method queries the persistent database for the content.
|
|
||||||
func (db *Database) preimage(hash common.Hash) ([]byte, error) { |
|
||||||
// Retrieve the node from cache if available
|
|
||||||
db.lock.RLock() |
|
||||||
preimage := db.preimages[hash] |
|
||||||
db.lock.RUnlock() |
|
||||||
|
|
||||||
if preimage != nil { |
|
||||||
return preimage, nil |
|
||||||
} |
|
||||||
// Content unavailable in memory, attempt to retrieve from disk
|
|
||||||
return db.diskdb.Get(db.secureKey(hash[:])) |
|
||||||
} |
|
||||||
|
|
||||||
// secureKey returns the database key for the preimage of key, as an ephemeral
|
|
||||||
// buffer. The caller must not hold onto the return value because it will become
|
|
||||||
// invalid on the next call.
|
|
||||||
func (db *Database) secureKey(key []byte) []byte { |
|
||||||
buf := append(db.seckeybuf[:0], secureKeyPrefix...) |
|
||||||
buf = append(buf, key...) |
|
||||||
return buf |
|
||||||
} |
|
||||||
|
|
||||||
// Nodes retrieves the hashes of all the nodes cached within the memory database.
|
|
||||||
// This method is extremely expensive and should only be used to validate internal
|
|
||||||
// states in test code.
|
|
||||||
func (db *Database) Nodes() []common.Hash { |
|
||||||
db.lock.RLock() |
|
||||||
defer db.lock.RUnlock() |
|
||||||
|
|
||||||
var hashes = make([]common.Hash, 0, len(db.nodes)) |
|
||||||
for hash := range db.nodes { |
|
||||||
if hash != (common.Hash{}) { // Special case for "root" references/nodes
|
|
||||||
hashes = append(hashes, hash) |
|
||||||
} |
|
||||||
} |
|
||||||
return hashes |
|
||||||
} |
|
||||||
|
|
||||||
// Reference adds a new reference from a parent node to a child node.
|
|
||||||
func (db *Database) Reference(child common.Hash, parent common.Hash) { |
|
||||||
db.lock.RLock() |
|
||||||
defer db.lock.RUnlock() |
|
||||||
|
|
||||||
db.reference(child, parent) |
|
||||||
} |
|
||||||
|
|
||||||
// reference is the private locked version of Reference.
|
|
||||||
func (db *Database) reference(child common.Hash, parent common.Hash) { |
|
||||||
// If the node does not exist, it's a node pulled from disk, skip
|
|
||||||
node, ok := db.nodes[child] |
|
||||||
if !ok { |
|
||||||
return |
|
||||||
} |
|
||||||
// If the reference already exists, only duplicate for roots
|
|
||||||
if db.nodes[parent].children == nil { |
|
||||||
db.nodes[parent].children = make(map[common.Hash]uint16) |
|
||||||
} else if _, ok = db.nodes[parent].children[child]; ok && parent != (common.Hash{}) { |
|
||||||
return |
|
||||||
} |
|
||||||
node.parents++ |
|
||||||
db.nodes[parent].children[child]++ |
|
||||||
} |
|
||||||
|
|
||||||
// Dereference removes an existing reference from a root node.
|
|
||||||
func (db *Database) Dereference(root common.Hash) { |
|
||||||
// Sanity check to ensure that the meta-root is not removed
|
|
||||||
if root == (common.Hash{}) { |
|
||||||
log.Error("Attempted to dereference the trie cache meta root") |
|
||||||
return |
|
||||||
} |
|
||||||
db.lock.Lock() |
|
||||||
defer db.lock.Unlock() |
|
||||||
|
|
||||||
nodes, storage, start := len(db.nodes), db.nodesSize, time.Now() |
|
||||||
db.dereference(root, common.Hash{}) |
|
||||||
|
|
||||||
db.gcnodes += uint64(nodes - len(db.nodes)) |
|
||||||
db.gcsize += storage - db.nodesSize |
|
||||||
db.gctime += time.Since(start) |
|
||||||
|
|
||||||
memcacheGCTimeTimer.Update(time.Since(start)) |
|
||||||
memcacheGCSizeMeter.Mark(int64(storage - db.nodesSize)) |
|
||||||
memcacheGCNodesMeter.Mark(int64(nodes - len(db.nodes))) |
|
||||||
|
|
||||||
log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.nodes), "size", storage-db.nodesSize, "time", time.Since(start), |
|
||||||
"gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.nodes), "livesize", db.nodesSize) |
|
||||||
} |
|
||||||
|
|
||||||
// dereference is the private locked version of Dereference.
|
|
||||||
func (db *Database) dereference(child common.Hash, parent common.Hash) { |
|
||||||
// Dereference the parent-child
|
|
||||||
node := db.nodes[parent] |
|
||||||
|
|
||||||
if node.children != nil && node.children[child] > 0 { |
|
||||||
node.children[child]-- |
|
||||||
if node.children[child] == 0 { |
|
||||||
delete(node.children, child) |
|
||||||
} |
|
||||||
} |
|
||||||
// If the child does not exist, it's a previously committed node.
|
|
||||||
node, ok := db.nodes[child] |
|
||||||
if !ok { |
|
||||||
return |
|
||||||
} |
|
||||||
// If there are no more references to the child, delete it and cascade
|
|
||||||
if node.parents > 0 { |
|
||||||
// This is a special cornercase where a node loaded from disk (i.e. not in the
|
|
||||||
// memcache any more) gets reinjected as a new node (short node split into full,
|
|
||||||
// then reverted into short), causing a cached node to have no parents. That is
|
|
||||||
// no problem in itself, but don't make maxint parents out of it.
|
|
||||||
node.parents-- |
|
||||||
} |
|
||||||
if node.parents == 0 { |
|
||||||
// Remove the node from the flush-list
|
|
||||||
switch child { |
|
||||||
case db.oldest: |
|
||||||
db.oldest = node.flushNext |
|
||||||
db.nodes[node.flushNext].flushPrev = common.Hash{} |
|
||||||
case db.newest: |
|
||||||
db.newest = node.flushPrev |
|
||||||
db.nodes[node.flushPrev].flushNext = common.Hash{} |
|
||||||
default: |
|
||||||
db.nodes[node.flushPrev].flushNext = node.flushNext |
|
||||||
db.nodes[node.flushNext].flushPrev = node.flushPrev |
|
||||||
} |
|
||||||
// Dereference all children and delete the node
|
|
||||||
for _, hash := range node.childs() { |
|
||||||
db.dereference(hash, child) |
|
||||||
} |
|
||||||
delete(db.nodes, child) |
|
||||||
db.nodesSize -= common.StorageSize(common.HashLength + int(node.size)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Cap iteratively flushes old but still referenced trie nodes until the total
|
|
||||||
// memory usage goes below the given threshold.
|
|
||||||
func (db *Database) Cap(limit common.StorageSize) error { |
|
||||||
// Create a database batch to flush persistent data out. It is important that
|
|
||||||
// outside code doesn't see an inconsistent state (referenced data removed from
|
|
||||||
// memory cache during commit but not yet in persistent storage). This is ensured
|
|
||||||
// by only uncaching existing data when the database write finalizes.
|
|
||||||
db.lock.RLock() |
|
||||||
|
|
||||||
nodes, storage, start := len(db.nodes), db.nodesSize, time.Now() |
|
||||||
batch := db.diskdb.NewBatch() |
|
||||||
|
|
||||||
// db.nodesSize only contains the useful data in the cache, but when reporting
|
|
||||||
// the total memory consumption, the maintenance metadata is also needed to be
|
|
||||||
// counted. For every useful node, we track 2 extra hashes as the flushlist.
|
|
||||||
size := db.nodesSize + common.StorageSize((len(db.nodes)-1)*2*common.HashLength) |
|
||||||
|
|
||||||
// If the preimage cache got large enough, push to disk. If it's still small
|
|
||||||
// leave for later to deduplicate writes.
|
|
||||||
flushPreimages := db.preimagesSize > 4*1024*1024 |
|
||||||
if flushPreimages { |
|
||||||
for hash, preimage := range db.preimages { |
|
||||||
if err := batch.Put(db.secureKey(hash[:]), preimage); err != nil { |
|
||||||
log.Error("Failed to commit preimage from trie database", "err", err) |
|
||||||
db.lock.RUnlock() |
|
||||||
return err |
|
||||||
} |
|
||||||
if batch.ValueSize() > hdb.IdealBatchSize { |
|
||||||
if err := batch.Write(); err != nil { |
|
||||||
db.lock.RUnlock() |
|
||||||
return err |
|
||||||
} |
|
||||||
batch.Reset() |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
// Keep committing nodes from the flush-list until we're below allowance
|
|
||||||
oldest := db.oldest |
|
||||||
for size > limit && oldest != (common.Hash{}) { |
|
||||||
// Fetch the oldest referenced node and push into the batch
|
|
||||||
node := db.nodes[oldest] |
|
||||||
if err := batch.Put(oldest[:], node.rlp()); err != nil { |
|
||||||
db.lock.RUnlock() |
|
||||||
return err |
|
||||||
} |
|
||||||
// If we exceeded the ideal batch size, commit and reset
|
|
||||||
if batch.ValueSize() >= hdb.IdealBatchSize { |
|
||||||
if err := batch.Write(); err != nil { |
|
||||||
log.Error("Failed to write flush list to disk", "err", err) |
|
||||||
db.lock.RUnlock() |
|
||||||
return err |
|
||||||
} |
|
||||||
batch.Reset() |
|
||||||
} |
|
||||||
// Iterate to the next flush item, or abort if the size cap was achieved. Size
|
|
||||||
// is the total size, including both the useful cached data (hash -> blob), as
|
|
||||||
// well as the flushlist metadata (2*hash). When flushing items from the cache,
|
|
||||||
// we need to reduce both.
|
|
||||||
size -= common.StorageSize(3*common.HashLength + int(node.size)) |
|
||||||
oldest = node.flushNext |
|
||||||
} |
|
||||||
// Flush out any remainder data from the last batch
|
|
||||||
if err := batch.Write(); err != nil { |
|
||||||
log.Error("Failed to write flush list to disk", "err", err) |
|
||||||
db.lock.RUnlock() |
|
||||||
return err |
|
||||||
} |
|
||||||
db.lock.RUnlock() |
|
||||||
|
|
||||||
// Write successful, clear out the flushed data
|
|
||||||
db.lock.Lock() |
|
||||||
defer db.lock.Unlock() |
|
||||||
|
|
||||||
if flushPreimages { |
|
||||||
db.preimages = make(map[common.Hash][]byte) |
|
||||||
db.preimagesSize = 0 |
|
||||||
} |
|
||||||
for db.oldest != oldest { |
|
||||||
node := db.nodes[db.oldest] |
|
||||||
delete(db.nodes, db.oldest) |
|
||||||
db.oldest = node.flushNext |
|
||||||
|
|
||||||
db.nodesSize -= common.StorageSize(common.HashLength + int(node.size)) |
|
||||||
} |
|
||||||
if db.oldest != (common.Hash{}) { |
|
||||||
db.nodes[db.oldest].flushPrev = common.Hash{} |
|
||||||
} |
|
||||||
db.flushnodes += uint64(nodes - len(db.nodes)) |
|
||||||
db.flushsize += storage - db.nodesSize |
|
||||||
db.flushtime += time.Since(start) |
|
||||||
|
|
||||||
memcacheFlushTimeTimer.Update(time.Since(start)) |
|
||||||
memcacheFlushSizeMeter.Mark(int64(storage - db.nodesSize)) |
|
||||||
memcacheFlushNodesMeter.Mark(int64(nodes - len(db.nodes))) |
|
||||||
|
|
||||||
log.Debug("Persisted nodes from memory database", "nodes", nodes-len(db.nodes), "size", storage-db.nodesSize, "time", time.Since(start), |
|
||||||
"flushnodes", db.flushnodes, "flushsize", db.flushsize, "flushtime", db.flushtime, "livenodes", len(db.nodes), "livesize", db.nodesSize) |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Commit iterates over all the children of a particular node, writes them out
|
|
||||||
// to disk, forcefully tearing down all references in both directions.
|
|
||||||
//
|
|
||||||
// As a side effect, all pre-images accumulated up to this point are also written.
|
|
||||||
func (db *Database) Commit(node common.Hash, report bool) error { |
|
||||||
// Create a database batch to flush persistent data out. It is important that
|
|
||||||
// outside code doesn't see an inconsistent state (referenced data removed from
|
|
||||||
// memory cache during commit but not yet in persistent storage). This is ensured
|
|
||||||
// by only uncaching existing data when the database write finalizes.
|
|
||||||
db.lock.RLock() |
|
||||||
|
|
||||||
start := time.Now() |
|
||||||
batch := db.diskdb.NewBatch() |
|
||||||
|
|
||||||
// Move all of the accumulated preimages into a write batch
|
|
||||||
for hash, preimage := range db.preimages { |
|
||||||
if err := batch.Put(db.secureKey(hash[:]), preimage); err != nil { |
|
||||||
log.Error("Failed to commit preimage from trie database", "err", err) |
|
||||||
db.lock.RUnlock() |
|
||||||
return err |
|
||||||
} |
|
||||||
if batch.ValueSize() > hdb.IdealBatchSize { |
|
||||||
if err := batch.Write(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
batch.Reset() |
|
||||||
} |
|
||||||
} |
|
||||||
// Move the trie itself into the batch, flushing if enough data is accumulated
|
|
||||||
nodes, storage := len(db.nodes), db.nodesSize |
|
||||||
if err := db.commit(node, batch); err != nil { |
|
||||||
log.Error("Failed to commit trie from trie database", "err", err) |
|
||||||
db.lock.RUnlock() |
|
||||||
return err |
|
||||||
} |
|
||||||
// Write batch ready, unlock for readers during persistence
|
|
||||||
if err := batch.Write(); err != nil { |
|
||||||
log.Error("Failed to write trie to disk", "err", err) |
|
||||||
db.lock.RUnlock() |
|
||||||
return err |
|
||||||
} |
|
||||||
db.lock.RUnlock() |
|
||||||
|
|
||||||
// Write successful, clear out the flushed data
|
|
||||||
db.lock.Lock() |
|
||||||
defer db.lock.Unlock() |
|
||||||
|
|
||||||
db.preimages = make(map[common.Hash][]byte) |
|
||||||
db.preimagesSize = 0 |
|
||||||
|
|
||||||
db.uncache(node) |
|
||||||
|
|
||||||
memcacheCommitTimeTimer.Update(time.Since(start)) |
|
||||||
memcacheCommitSizeMeter.Mark(int64(storage - db.nodesSize)) |
|
||||||
memcacheCommitNodesMeter.Mark(int64(nodes - len(db.nodes))) |
|
||||||
|
|
||||||
logger := log.Info |
|
||||||
if !report { |
|
||||||
logger = log.Debug |
|
||||||
} |
|
||||||
logger("Persisted trie from memory database", "nodes", nodes-len(db.nodes)+int(db.flushnodes), "size", storage-db.nodesSize+db.flushsize, "time", time.Since(start)+db.flushtime, |
|
||||||
"gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", db.gctime, "livenodes", len(db.nodes), "livesize", db.nodesSize) |
|
||||||
|
|
||||||
// Reset the garbage collection statistics
|
|
||||||
db.gcnodes, db.gcsize, db.gctime = 0, 0, 0 |
|
||||||
db.flushnodes, db.flushsize, db.flushtime = 0, 0, 0 |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// commit is the private locked version of Commit.
|
|
||||||
func (db *Database) commit(hash common.Hash, batch hdb.Batch) error { |
|
||||||
// If the node does not exist, it's a previously committed node
|
|
||||||
node, ok := db.nodes[hash] |
|
||||||
if !ok { |
|
||||||
return nil |
|
||||||
} |
|
||||||
for _, child := range node.childs() { |
|
||||||
if err := db.commit(child, batch); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
if err := batch.Put(hash[:], node.rlp()); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
// If we've reached an optimal batch size, commit and start over
|
|
||||||
if batch.ValueSize() >= hdb.IdealBatchSize { |
|
||||||
if err := batch.Write(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
batch.Reset() |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// uncache is the post-processing step of a commit operation where the already
|
|
||||||
// persisted trie is removed from the cache. The reason behind the two-phase
|
|
||||||
// commit is to ensure consistent data availability while moving from memory
|
|
||||||
// to disk.
|
|
||||||
func (db *Database) uncache(hash common.Hash) { |
|
||||||
// If the node does not exist, we're done on this path
|
|
||||||
node, ok := db.nodes[hash] |
|
||||||
if !ok { |
|
||||||
return |
|
||||||
} |
|
||||||
// Node still exists, remove it from the flush-list
|
|
||||||
switch hash { |
|
||||||
case db.oldest: |
|
||||||
db.oldest = node.flushNext |
|
||||||
db.nodes[node.flushNext].flushPrev = common.Hash{} |
|
||||||
case db.newest: |
|
||||||
db.newest = node.flushPrev |
|
||||||
db.nodes[node.flushPrev].flushNext = common.Hash{} |
|
||||||
default: |
|
||||||
db.nodes[node.flushPrev].flushNext = node.flushNext |
|
||||||
db.nodes[node.flushNext].flushPrev = node.flushPrev |
|
||||||
} |
|
||||||
// Uncache the node's subtries and remove the node itself too
|
|
||||||
for _, child := range node.childs() { |
|
||||||
db.uncache(child) |
|
||||||
} |
|
||||||
delete(db.nodes, hash) |
|
||||||
db.nodesSize -= common.StorageSize(common.HashLength + int(node.size)) |
|
||||||
} |
|
||||||
|
|
||||||
// Size returns the current storage size of the memory cache in front of the
|
|
||||||
// persistent database layer.
|
|
||||||
func (db *Database) Size() (common.StorageSize, common.StorageSize) { |
|
||||||
db.lock.RLock() |
|
||||||
defer db.lock.RUnlock() |
|
||||||
|
|
||||||
// db.nodesSize only contains the useful data in the cache, but when reporting
|
|
||||||
// the total memory consumption, the maintenance metadata is also needed to be
|
|
||||||
// counted. For every useful node, we track 2 extra hashes as the flushlist.
|
|
||||||
var flushlistSize = common.StorageSize((len(db.nodes) - 1) * 2 * common.HashLength) |
|
||||||
return db.nodesSize + flushlistSize, db.preimagesSize |
|
||||||
} |
|
||||||
|
|
||||||
// verifyIntegrity is a debug method to iterate over the entire trie stored in
|
|
||||||
// memory and check whether every node is reachable from the meta root. The goal
|
|
||||||
// is to find any errors that might cause memory leaks and or trie nodes to go
|
|
||||||
// missing.
|
|
||||||
//
|
|
||||||
// This method is extremely CPU and memory intensive, only use when must.
|
|
||||||
func (db *Database) verifyIntegrity() { |
|
||||||
// Iterate over all the cached nodes and accumulate them into a set
|
|
||||||
reachable := map[common.Hash]struct{}{{}: {}} |
|
||||||
|
|
||||||
for child := range db.nodes[common.Hash{}].children { |
|
||||||
db.accumulate(child, reachable) |
|
||||||
} |
|
||||||
// Find any unreachable but cached nodes
|
|
||||||
unreachable := []string{} |
|
||||||
for hash, node := range db.nodes { |
|
||||||
if _, ok := reachable[hash]; !ok { |
|
||||||
unreachable = append(unreachable, fmt.Sprintf("%x: {Node: %v, Parents: %d, Prev: %x, Next: %x}", |
|
||||||
hash, node.node, node.parents, node.flushPrev, node.flushNext)) |
|
||||||
} |
|
||||||
} |
|
||||||
if len(unreachable) != 0 { |
|
||||||
panic(fmt.Sprintf("trie cache memory leak: %v", unreachable)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// accumulate iterates over the trie defined by hash and accumulates all the
|
|
||||||
// cached children found in memory.
|
|
||||||
func (db *Database) accumulate(hash common.Hash, reachable map[common.Hash]struct{}) { |
|
||||||
// Mark the node reachable if present in the memory cache
|
|
||||||
node, ok := db.nodes[hash] |
|
||||||
if !ok { |
|
||||||
return |
|
||||||
} |
|
||||||
reachable[hash] = struct{}{} |
|
||||||
|
|
||||||
// Iterate over all the children and accumulate them too
|
|
||||||
for _, child := range node.childs() { |
|
||||||
db.accumulate(child, reachable) |
|
||||||
} |
|
||||||
} |
|
@ -1,113 +0,0 @@ |
|||||||
// Copyright 2014 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 trie |
|
||||||
|
|
||||||
// Trie keys are dealt with in three distinct encodings:
|
|
||||||
//
|
|
||||||
// KEYBYTES encoding contains the actual key and nothing else. This encoding is the
|
|
||||||
// input to most API functions.
|
|
||||||
//
|
|
||||||
// HEX encoding contains one byte for each nibble of the key and an optional trailing
|
|
||||||
// 'terminator' byte of value 0x10 which indicates whether or not the node at the key
|
|
||||||
// contains a value. Hex key encoding is used for nodes loaded in memory because it's
|
|
||||||
// convenient to access.
|
|
||||||
//
|
|
||||||
// COMPACT encoding is defined by the Ethereum Yellow Paper (it's called "hex prefix
|
|
||||||
// encoding" there) and contains the bytes of the key and a flag. The high nibble of the
|
|
||||||
// first byte contains the flag; the lowest bit encoding the oddness of the length and
|
|
||||||
// the second-lowest encoding whether the node at the key is a value node. The low nibble
|
|
||||||
// of the first byte is zero in the case of an even number of nibbles and the first nibble
|
|
||||||
// in the case of an odd number. All remaining nibbles (now an even number) fit properly
|
|
||||||
// into the remaining bytes. Compact encoding is used for nodes stored on disk.
|
|
||||||
|
|
||||||
func hexToCompact(hex []byte) []byte { |
|
||||||
terminator := byte(0) |
|
||||||
if hasTerm(hex) { |
|
||||||
terminator = 1 |
|
||||||
hex = hex[:len(hex)-1] |
|
||||||
} |
|
||||||
buf := make([]byte, len(hex)/2+1) |
|
||||||
buf[0] = terminator << 5 // the flag byte
|
|
||||||
if len(hex)&1 == 1 { |
|
||||||
buf[0] |= 1 << 4 // odd flag
|
|
||||||
buf[0] |= hex[0] // first nibble is contained in the first byte
|
|
||||||
hex = hex[1:] |
|
||||||
} |
|
||||||
decodeNibbles(hex, buf[1:]) |
|
||||||
return buf |
|
||||||
} |
|
||||||
|
|
||||||
func compactToHex(compact []byte) []byte { |
|
||||||
base := keybytesToHex(compact) |
|
||||||
// delete terminator flag
|
|
||||||
if base[0] < 2 { |
|
||||||
base = base[:len(base)-1] |
|
||||||
} |
|
||||||
// apply odd flag
|
|
||||||
chop := 2 - base[0]&1 |
|
||||||
return base[chop:] |
|
||||||
} |
|
||||||
|
|
||||||
func keybytesToHex(str []byte) []byte { |
|
||||||
l := len(str)*2 + 1 |
|
||||||
var nibbles = make([]byte, l) |
|
||||||
for i, b := range str { |
|
||||||
nibbles[i*2] = b / 16 |
|
||||||
nibbles[i*2+1] = b % 16 |
|
||||||
} |
|
||||||
nibbles[l-1] = 16 |
|
||||||
return nibbles |
|
||||||
} |
|
||||||
|
|
||||||
// hexToKeybytes turns hex nibbles into key bytes.
|
|
||||||
// This can only be used for keys of even length.
|
|
||||||
func hexToKeybytes(hex []byte) []byte { |
|
||||||
if hasTerm(hex) { |
|
||||||
hex = hex[:len(hex)-1] |
|
||||||
} |
|
||||||
if len(hex)&1 != 0 { |
|
||||||
panic("can't convert hex key of odd length") |
|
||||||
} |
|
||||||
key := make([]byte, len(hex)/2) |
|
||||||
decodeNibbles(hex, key) |
|
||||||
return key |
|
||||||
} |
|
||||||
|
|
||||||
func decodeNibbles(nibbles []byte, bytes []byte) { |
|
||||||
for bi, ni := 0, 0; ni < len(nibbles); bi, ni = bi+1, ni+2 { |
|
||||||
bytes[bi] = nibbles[ni]<<4 | nibbles[ni+1] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// prefixLen returns the length of the common prefix of a and b.
|
|
||||||
func prefixLen(a, b []byte) int { |
|
||||||
var i, length = 0, len(a) |
|
||||||
if len(b) < length { |
|
||||||
length = len(b) |
|
||||||
} |
|
||||||
for ; i < length; i++ { |
|
||||||
if a[i] != b[i] { |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
return i |
|
||||||
} |
|
||||||
|
|
||||||
// hasTerm returns whether a hex key has the terminator flag.
|
|
||||||
func hasTerm(s []byte) bool { |
|
||||||
return len(s) > 0 && s[len(s)-1] == 16 |
|
||||||
} |
|
@ -1,104 +0,0 @@ |
|||||||
// Copyright 2014 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestHexCompact(t *testing.T) { |
|
||||||
tests := []struct{ hex, compact []byte }{ |
|
||||||
// empty keys, with and without terminator.
|
|
||||||
{hex: []byte{}, compact: []byte{0x00}}, |
|
||||||
{hex: []byte{16}, compact: []byte{0x20}}, |
|
||||||
// odd length, no terminator
|
|
||||||
{hex: []byte{1, 2, 3, 4, 5}, compact: []byte{0x11, 0x23, 0x45}}, |
|
||||||
// even length, no terminator
|
|
||||||
{hex: []byte{0, 1, 2, 3, 4, 5}, compact: []byte{0x00, 0x01, 0x23, 0x45}}, |
|
||||||
// odd length, terminator
|
|
||||||
{hex: []byte{15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x3f, 0x1c, 0xb8}}, |
|
||||||
// even length, terminator
|
|
||||||
{hex: []byte{0, 15, 1, 12, 11, 8, 16 /*term*/}, compact: []byte{0x20, 0x0f, 0x1c, 0xb8}}, |
|
||||||
} |
|
||||||
for _, test := range tests { |
|
||||||
if c := hexToCompact(test.hex); !bytes.Equal(c, test.compact) { |
|
||||||
t.Errorf("hexToCompact(%x) -> %x, want %x", test.hex, c, test.compact) |
|
||||||
} |
|
||||||
if h := compactToHex(test.compact); !bytes.Equal(h, test.hex) { |
|
||||||
t.Errorf("compactToHex(%x) -> %x, want %x", test.compact, h, test.hex) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHexKeybytes(t *testing.T) { |
|
||||||
tests := []struct{ key, hexIn, hexOut []byte }{ |
|
||||||
{key: []byte{}, hexIn: []byte{16}, hexOut: []byte{16}}, |
|
||||||
{key: []byte{}, hexIn: []byte{}, hexOut: []byte{16}}, |
|
||||||
{ |
|
||||||
key: []byte{0x12, 0x34, 0x56}, |
|
||||||
hexIn: []byte{1, 2, 3, 4, 5, 6, 16}, |
|
||||||
hexOut: []byte{1, 2, 3, 4, 5, 6, 16}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
key: []byte{0x12, 0x34, 0x5}, |
|
||||||
hexIn: []byte{1, 2, 3, 4, 0, 5, 16}, |
|
||||||
hexOut: []byte{1, 2, 3, 4, 0, 5, 16}, |
|
||||||
}, |
|
||||||
{ |
|
||||||
key: []byte{0x12, 0x34, 0x56}, |
|
||||||
hexIn: []byte{1, 2, 3, 4, 5, 6}, |
|
||||||
hexOut: []byte{1, 2, 3, 4, 5, 6, 16}, |
|
||||||
}, |
|
||||||
} |
|
||||||
for _, test := range tests { |
|
||||||
if h := keybytesToHex(test.key); !bytes.Equal(h, test.hexOut) { |
|
||||||
t.Errorf("keybytesToHex(%x) -> %x, want %x", test.key, h, test.hexOut) |
|
||||||
} |
|
||||||
if k := hexToKeybytes(test.hexIn); !bytes.Equal(k, test.key) { |
|
||||||
t.Errorf("hexToKeybytes(%x) -> %x, want %x", test.hexIn, k, test.key) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func BenchmarkHexToCompact(b *testing.B) { |
|
||||||
testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/} |
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
hexToCompact(testBytes) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func BenchmarkCompactToHex(b *testing.B) { |
|
||||||
testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/} |
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
compactToHex(testBytes) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func BenchmarkKeybytesToHex(b *testing.B) { |
|
||||||
testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16} |
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
keybytesToHex(testBytes) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func BenchmarkHexToKeybytes(b *testing.B) { |
|
||||||
testBytes := []byte{7, 6, 6, 5, 7, 2, 6, 2, 16} |
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
hexToKeybytes(testBytes) |
|
||||||
} |
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
// Copyright 2015 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
) |
|
||||||
|
|
||||||
// MissingNodeError is returned by the trie functions (TryGet, TryUpdate, TryDelete)
|
|
||||||
// in the case where a trie node is not present in the local database. It contains
|
|
||||||
// information necessary for retrieving the missing node.
|
|
||||||
type MissingNodeError struct { |
|
||||||
NodeHash common.Hash // hash of the missing node
|
|
||||||
Path []byte // hex-encoded path to the missing node
|
|
||||||
} |
|
||||||
|
|
||||||
func (err *MissingNodeError) Error() string { |
|
||||||
return fmt.Sprintf("missing trie node %x (path %x)", err.NodeHash, err.Path) |
|
||||||
} |
|
@ -1,218 +0,0 @@ |
|||||||
// Copyright 2016 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"hash" |
|
||||||
"sync" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/rlp" |
|
||||||
"golang.org/x/crypto/sha3" |
|
||||||
) |
|
||||||
|
|
||||||
type hasher struct { |
|
||||||
tmp sliceBuffer |
|
||||||
sha keccakState |
|
||||||
cachegen uint16 |
|
||||||
cachelimit uint16 |
|
||||||
onleaf LeafCallback |
|
||||||
} |
|
||||||
|
|
||||||
// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
|
|
||||||
// Read to get a variable amount of data from the hash state. Read is faster than Sum
|
|
||||||
// because it doesn't copy the internal state, but also modifies the internal state.
|
|
||||||
type keccakState interface { |
|
||||||
hash.Hash |
|
||||||
Read([]byte) (int, error) |
|
||||||
} |
|
||||||
|
|
||||||
type sliceBuffer []byte |
|
||||||
|
|
||||||
func (b *sliceBuffer) Write(data []byte) (n int, err error) { |
|
||||||
*b = append(*b, data...) |
|
||||||
return len(data), nil |
|
||||||
} |
|
||||||
|
|
||||||
func (b *sliceBuffer) Reset() { |
|
||||||
*b = (*b)[:0] |
|
||||||
} |
|
||||||
|
|
||||||
// hashers live in a global db.
|
|
||||||
var hasherPool = sync.Pool{ |
|
||||||
New: func() interface{} { |
|
||||||
return &hasher{ |
|
||||||
tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
|
|
||||||
sha: sha3.NewLegacyKeccak256().(keccakState), |
|
||||||
} |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
func newHasher(cachegen, cachelimit uint16, onleaf LeafCallback) *hasher { |
|
||||||
h := hasherPool.Get().(*hasher) |
|
||||||
h.cachegen, h.cachelimit, h.onleaf = cachegen, cachelimit, onleaf |
|
||||||
return h |
|
||||||
} |
|
||||||
|
|
||||||
func returnHasherToPool(h *hasher) { |
|
||||||
hasherPool.Put(h) |
|
||||||
} |
|
||||||
|
|
||||||
// hash collapses a node down into a hash node, also returning a copy of the
|
|
||||||
// original node initialized with the computed hash to replace the original one.
|
|
||||||
func (h *hasher) hash(n node, db *Database, force bool) (node, node, error) { |
|
||||||
// If we're not storing the node, just hashing, use available cached data
|
|
||||||
if hash, dirty := n.cache(); hash != nil { |
|
||||||
if db == nil { |
|
||||||
return hash, n, nil |
|
||||||
} |
|
||||||
if n.canUnload(h.cachegen, h.cachelimit) { |
|
||||||
// Unload the node from cache. All of its subnodes will have a lower or equal
|
|
||||||
// cache generation number.
|
|
||||||
cacheUnloadCounter.Inc(1) |
|
||||||
return hash, hash, nil |
|
||||||
} |
|
||||||
if !dirty { |
|
||||||
return hash, n, nil |
|
||||||
} |
|
||||||
} |
|
||||||
// Trie not processed yet or needs storage, walk the children
|
|
||||||
collapsed, cached, err := h.hashChildren(n, db) |
|
||||||
if err != nil { |
|
||||||
return hashNode{}, n, err |
|
||||||
} |
|
||||||
hashed, err := h.store(collapsed, db, force) |
|
||||||
if err != nil { |
|
||||||
return hashNode{}, n, err |
|
||||||
} |
|
||||||
// Cache the hash of the node for later reuse and remove
|
|
||||||
// the dirty flag in commit mode. It's fine to assign these values directly
|
|
||||||
// without copying the node first because hashChildren copies it.
|
|
||||||
cachedHash, _ := hashed.(hashNode) |
|
||||||
switch cn := cached.(type) { |
|
||||||
case *shortNode: |
|
||||||
cn.flags.hash = cachedHash |
|
||||||
if db != nil { |
|
||||||
cn.flags.dirty = false |
|
||||||
} |
|
||||||
case *fullNode: |
|
||||||
cn.flags.hash = cachedHash |
|
||||||
if db != nil { |
|
||||||
cn.flags.dirty = false |
|
||||||
} |
|
||||||
} |
|
||||||
return hashed, cached, nil |
|
||||||
} |
|
||||||
|
|
||||||
// hashChildren replaces the children of a node with their hashes if the encoded
|
|
||||||
// size of the child is larger than a hash, returning the collapsed node as well
|
|
||||||
// as a replacement for the original node with the child hashes cached in.
|
|
||||||
func (h *hasher) hashChildren(original node, db *Database) (node, node, error) { |
|
||||||
var err error |
|
||||||
|
|
||||||
switch n := original.(type) { |
|
||||||
case *shortNode: |
|
||||||
// Hash the short node's child, caching the newly hashed subtree
|
|
||||||
collapsed, cached := n.copy(), n.copy() |
|
||||||
collapsed.Key = hexToCompact(n.Key) |
|
||||||
cached.Key = common.CopyBytes(n.Key) |
|
||||||
|
|
||||||
if _, ok := n.Val.(valueNode); !ok { |
|
||||||
collapsed.Val, cached.Val, err = h.hash(n.Val, db, false) |
|
||||||
if err != nil { |
|
||||||
return original, original, err |
|
||||||
} |
|
||||||
} |
|
||||||
return collapsed, cached, nil |
|
||||||
|
|
||||||
case *fullNode: |
|
||||||
// Hash the full node's children, caching the newly hashed subtrees
|
|
||||||
collapsed, cached := n.copy(), n.copy() |
|
||||||
|
|
||||||
for i := 0; i < 16; i++ { |
|
||||||
if n.Children[i] != nil { |
|
||||||
collapsed.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false) |
|
||||||
if err != nil { |
|
||||||
return original, original, err |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
cached.Children[16] = n.Children[16] |
|
||||||
return collapsed, cached, nil |
|
||||||
|
|
||||||
default: |
|
||||||
// Value and hash nodes don't have children so they're left as were
|
|
||||||
return n, original, nil |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// store hashes the node n and if we have a storage layer specified, it writes
|
|
||||||
// the key/value pair to it and tracks any node->child references as well as any
|
|
||||||
// node->external trie references.
|
|
||||||
func (h *hasher) store(n node, db *Database, force bool) (node, error) { |
|
||||||
// Don't store hashes or empty nodes.
|
|
||||||
if _, isHash := n.(hashNode); n == nil || isHash { |
|
||||||
return n, nil |
|
||||||
} |
|
||||||
// Generate the RLP encoding of the node
|
|
||||||
h.tmp.Reset() |
|
||||||
if err := rlp.Encode(&h.tmp, n); err != nil { |
|
||||||
panic("encode error: " + err.Error()) |
|
||||||
} |
|
||||||
if len(h.tmp) < 32 && !force { |
|
||||||
return n, nil // Nodes smaller than 32 bytes are stored inside their parent
|
|
||||||
} |
|
||||||
// Larger nodes are replaced by their hash and stored in the database.
|
|
||||||
hash, _ := n.cache() |
|
||||||
if hash == nil { |
|
||||||
hash = h.makeHashNode(h.tmp) |
|
||||||
} |
|
||||||
|
|
||||||
if db != nil { |
|
||||||
// We are pooling the trie nodes into an intermediate memory cache
|
|
||||||
hash := common.BytesToHash(hash) |
|
||||||
|
|
||||||
db.lock.Lock() |
|
||||||
db.insert(hash, h.tmp, n) |
|
||||||
db.lock.Unlock() |
|
||||||
|
|
||||||
// Track external references from account->storage trie
|
|
||||||
if h.onleaf != nil { |
|
||||||
switch n := n.(type) { |
|
||||||
case *shortNode: |
|
||||||
if child, ok := n.Val.(valueNode); ok { |
|
||||||
h.onleaf(child, hash) |
|
||||||
} |
|
||||||
case *fullNode: |
|
||||||
for i := 0; i < 16; i++ { |
|
||||||
if child, ok := n.Children[i].(valueNode); ok { |
|
||||||
h.onleaf(child, hash) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return hash, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (h *hasher) makeHashNode(data []byte) hashNode { |
|
||||||
n := make(hashNode, h.sha.Size()) |
|
||||||
h.sha.Reset() |
|
||||||
h.sha.Write(data) |
|
||||||
h.sha.Read(n) |
|
||||||
return n |
|
||||||
} |
|
@ -1,575 +0,0 @@ |
|||||||
// Copyright 2014 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"container/heap" |
|
||||||
"errors" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/rlp" |
|
||||||
) |
|
||||||
|
|
||||||
// Iterator is a key-value trie iterator that traverses a Trie.
|
|
||||||
type Iterator struct { |
|
||||||
nodeIt NodeIterator |
|
||||||
|
|
||||||
Key []byte // Current data key on which the iterator is positioned on
|
|
||||||
Value []byte // Current data value on which the iterator is positioned on
|
|
||||||
Err error |
|
||||||
} |
|
||||||
|
|
||||||
// NewIterator creates a new key-value iterator from a node iterator
|
|
||||||
func NewIterator(it NodeIterator) *Iterator { |
|
||||||
return &Iterator{ |
|
||||||
nodeIt: it, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Next moves the iterator forward one key-value entry.
|
|
||||||
func (it *Iterator) Next() bool { |
|
||||||
for it.nodeIt.Next(true) { |
|
||||||
if it.nodeIt.Leaf() { |
|
||||||
it.Key = it.nodeIt.LeafKey() |
|
||||||
it.Value = it.nodeIt.LeafBlob() |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
it.Key = nil |
|
||||||
it.Value = nil |
|
||||||
it.Err = it.nodeIt.Error() |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// Prove generates the Merkle proof for the leaf node the iterator is currently
|
|
||||||
// positioned on.
|
|
||||||
func (it *Iterator) Prove() [][]byte { |
|
||||||
return it.nodeIt.LeafProof() |
|
||||||
} |
|
||||||
|
|
||||||
// NodeIterator is an iterator to traverse the trie pre-order.
|
|
||||||
type NodeIterator interface { |
|
||||||
// Next moves the iterator to the next node. If the parameter is false, any child
|
|
||||||
// nodes will be skipped.
|
|
||||||
Next(bool) bool |
|
||||||
|
|
||||||
// Error returns the error status of the iterator.
|
|
||||||
Error() error |
|
||||||
|
|
||||||
// Hash returns the hash of the current node.
|
|
||||||
Hash() common.Hash |
|
||||||
|
|
||||||
// Parent returns the hash of the parent of the current node. The hash may be the one
|
|
||||||
// grandparent if the immediate parent is an internal node with no hash.
|
|
||||||
Parent() common.Hash |
|
||||||
|
|
||||||
// Path returns the hex-encoded path to the current node.
|
|
||||||
// Callers must not retain references to the return value after calling Next.
|
|
||||||
// For leaf nodes, the last element of the path is the 'terminator symbol' 0x10.
|
|
||||||
Path() []byte |
|
||||||
|
|
||||||
// Leaf returns true iff the current node is a leaf node.
|
|
||||||
Leaf() bool |
|
||||||
|
|
||||||
// LeafKey returns the key of the leaf. The method panics if the iterator is not
|
|
||||||
// positioned at a leaf. Callers must not retain references to the value after
|
|
||||||
// calling Next.
|
|
||||||
LeafKey() []byte |
|
||||||
|
|
||||||
// LeafBlob returns the content of the leaf. The method panics if the iterator
|
|
||||||
// is not positioned at a leaf. Callers must not retain references to the value
|
|
||||||
// after calling Next.
|
|
||||||
LeafBlob() []byte |
|
||||||
|
|
||||||
// LeafProof returns the Merkle proof of the leaf. The method panics if the
|
|
||||||
// iterator is not positioned at a leaf. Callers must not retain references
|
|
||||||
// to the value after calling Next.
|
|
||||||
LeafProof() [][]byte |
|
||||||
} |
|
||||||
|
|
||||||
// nodeIteratorState represents the iteration state at one particular node of the
|
|
||||||
// trie, which can be resumed at a later invocation.
|
|
||||||
type nodeIteratorState struct { |
|
||||||
hash common.Hash // Hash of the node being iterated (nil if not standalone)
|
|
||||||
node node // Trie node being iterated
|
|
||||||
parent common.Hash // Hash of the first full ancestor node (nil if current is the root)
|
|
||||||
index int // Child to be processed next
|
|
||||||
pathlen int // Length of the path to this node
|
|
||||||
} |
|
||||||
|
|
||||||
type nodeIterator struct { |
|
||||||
trie *Trie // Trie being iterated
|
|
||||||
stack []*nodeIteratorState // Hierarchy of trie nodes persisting the iteration state
|
|
||||||
path []byte // Path to the current node
|
|
||||||
err error // Failure set in case of an internal error in the iterator
|
|
||||||
} |
|
||||||
|
|
||||||
// errIteratorEnd is stored in nodeIterator.err when iteration is done.
|
|
||||||
var errIteratorEnd = errors.New("end of iteration") |
|
||||||
|
|
||||||
// seekError is stored in nodeIterator.err if the initial seek has failed.
|
|
||||||
type seekError struct { |
|
||||||
key []byte |
|
||||||
err error |
|
||||||
} |
|
||||||
|
|
||||||
func (e seekError) Error() string { |
|
||||||
return "seek error: " + e.err.Error() |
|
||||||
} |
|
||||||
|
|
||||||
func newNodeIterator(trie *Trie, start []byte) NodeIterator { |
|
||||||
if trie.Hash() == emptyState { |
|
||||||
return new(nodeIterator) |
|
||||||
} |
|
||||||
it := &nodeIterator{trie: trie} |
|
||||||
it.err = it.seek(start) |
|
||||||
return it |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) Hash() common.Hash { |
|
||||||
if len(it.stack) == 0 { |
|
||||||
return common.Hash{} |
|
||||||
} |
|
||||||
return it.stack[len(it.stack)-1].hash |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) Parent() common.Hash { |
|
||||||
if len(it.stack) == 0 { |
|
||||||
return common.Hash{} |
|
||||||
} |
|
||||||
return it.stack[len(it.stack)-1].parent |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) Leaf() bool { |
|
||||||
return hasTerm(it.path) |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) LeafKey() []byte { |
|
||||||
if len(it.stack) > 0 { |
|
||||||
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { |
|
||||||
return hexToKeybytes(it.path) |
|
||||||
} |
|
||||||
} |
|
||||||
panic("not at leaf") |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) LeafBlob() []byte { |
|
||||||
if len(it.stack) > 0 { |
|
||||||
if node, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { |
|
||||||
return []byte(node) |
|
||||||
} |
|
||||||
} |
|
||||||
panic("not at leaf") |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) LeafProof() [][]byte { |
|
||||||
if len(it.stack) > 0 { |
|
||||||
if _, ok := it.stack[len(it.stack)-1].node.(valueNode); ok { |
|
||||||
hasher := newHasher(0, 0, nil) |
|
||||||
proofs := make([][]byte, 0, len(it.stack)) |
|
||||||
|
|
||||||
for i, item := range it.stack[:len(it.stack)-1] { |
|
||||||
// Gather nodes that end up as hash nodes (or the root)
|
|
||||||
node, _, _ := hasher.hashChildren(item.node, nil) |
|
||||||
hashed, _ := hasher.store(node, nil, false) |
|
||||||
if _, ok := hashed.(hashNode); ok || i == 0 { |
|
||||||
enc, _ := rlp.EncodeToBytes(node) |
|
||||||
proofs = append(proofs, enc) |
|
||||||
} |
|
||||||
} |
|
||||||
return proofs |
|
||||||
} |
|
||||||
} |
|
||||||
panic("not at leaf") |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) Path() []byte { |
|
||||||
return it.path |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) Error() error { |
|
||||||
if it.err == errIteratorEnd { |
|
||||||
return nil |
|
||||||
} |
|
||||||
if seek, ok := it.err.(seekError); ok { |
|
||||||
return seek.err |
|
||||||
} |
|
||||||
return it.err |
|
||||||
} |
|
||||||
|
|
||||||
// Next moves the iterator to the next node, returning whether there are any
|
|
||||||
// further nodes. In case of an internal error this method returns false and
|
|
||||||
// sets the Error field to the encountered failure. If `descend` is false,
|
|
||||||
// skips iterating over any subnodes of the current node.
|
|
||||||
func (it *nodeIterator) Next(descend bool) bool { |
|
||||||
if it.err == errIteratorEnd { |
|
||||||
return false |
|
||||||
} |
|
||||||
if seek, ok := it.err.(seekError); ok { |
|
||||||
if it.err = it.seek(seek.key); it.err != nil { |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
// Otherwise step forward with the iterator and report any errors.
|
|
||||||
state, parentIndex, path, err := it.peek(descend) |
|
||||||
it.err = err |
|
||||||
if it.err != nil { |
|
||||||
return false |
|
||||||
} |
|
||||||
it.push(state, parentIndex, path) |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) seek(prefix []byte) error { |
|
||||||
// The path we're looking for is the hex encoded key without terminator.
|
|
||||||
key := keybytesToHex(prefix) |
|
||||||
key = key[:len(key)-1] |
|
||||||
// Move forward until we're just before the closest match to key.
|
|
||||||
for { |
|
||||||
state, parentIndex, path, err := it.peek(bytes.HasPrefix(key, it.path)) |
|
||||||
if err == errIteratorEnd { |
|
||||||
return errIteratorEnd |
|
||||||
} else if err != nil { |
|
||||||
return seekError{prefix, err} |
|
||||||
} else if bytes.Compare(path, key) >= 0 { |
|
||||||
return nil |
|
||||||
} |
|
||||||
it.push(state, parentIndex, path) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// peek creates the next state of the iterator.
|
|
||||||
func (it *nodeIterator) peek(descend bool) (*nodeIteratorState, *int, []byte, error) { |
|
||||||
if len(it.stack) == 0 { |
|
||||||
// Initialize the iterator if we've just started.
|
|
||||||
root := it.trie.Hash() |
|
||||||
state := &nodeIteratorState{node: it.trie.root, index: -1} |
|
||||||
if root != emptyRoot { |
|
||||||
state.hash = root |
|
||||||
} |
|
||||||
err := state.resolve(it.trie, nil) |
|
||||||
return state, nil, nil, err |
|
||||||
} |
|
||||||
if !descend { |
|
||||||
// If we're skipping children, pop the current node first
|
|
||||||
it.pop() |
|
||||||
} |
|
||||||
|
|
||||||
// Continue iteration to the next child
|
|
||||||
for len(it.stack) > 0 { |
|
||||||
parent := it.stack[len(it.stack)-1] |
|
||||||
ancestor := parent.hash |
|
||||||
if (ancestor == common.Hash{}) { |
|
||||||
ancestor = parent.parent |
|
||||||
} |
|
||||||
state, path, ok := it.nextChild(parent, ancestor) |
|
||||||
if ok { |
|
||||||
if err := state.resolve(it.trie, path); err != nil { |
|
||||||
return parent, &parent.index, path, err |
|
||||||
} |
|
||||||
return state, &parent.index, path, nil |
|
||||||
} |
|
||||||
// No more child nodes, move back up.
|
|
||||||
it.pop() |
|
||||||
} |
|
||||||
return nil, nil, nil, errIteratorEnd |
|
||||||
} |
|
||||||
|
|
||||||
func (st *nodeIteratorState) resolve(tr *Trie, path []byte) error { |
|
||||||
if hash, ok := st.node.(hashNode); ok { |
|
||||||
resolved, err := tr.resolveHash(hash, path) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
st.node = resolved |
|
||||||
st.hash = common.BytesToHash(hash) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Hash) (*nodeIteratorState, []byte, bool) { |
|
||||||
switch node := parent.node.(type) { |
|
||||||
case *fullNode: |
|
||||||
// Full node, move to the first non-nil child.
|
|
||||||
for i := parent.index + 1; i < len(node.Children); i++ { |
|
||||||
child := node.Children[i] |
|
||||||
if child != nil { |
|
||||||
hash, _ := child.cache() |
|
||||||
state := &nodeIteratorState{ |
|
||||||
hash: common.BytesToHash(hash), |
|
||||||
node: child, |
|
||||||
parent: ancestor, |
|
||||||
index: -1, |
|
||||||
pathlen: len(it.path), |
|
||||||
} |
|
||||||
path := append(it.path, byte(i)) |
|
||||||
parent.index = i - 1 |
|
||||||
return state, path, true |
|
||||||
} |
|
||||||
} |
|
||||||
case *shortNode: |
|
||||||
// Short node, return the pointer singleton child
|
|
||||||
if parent.index < 0 { |
|
||||||
hash, _ := node.Val.cache() |
|
||||||
state := &nodeIteratorState{ |
|
||||||
hash: common.BytesToHash(hash), |
|
||||||
node: node.Val, |
|
||||||
parent: ancestor, |
|
||||||
index: -1, |
|
||||||
pathlen: len(it.path), |
|
||||||
} |
|
||||||
path := append(it.path, node.Key...) |
|
||||||
return state, path, true |
|
||||||
} |
|
||||||
} |
|
||||||
return parent, it.path, false |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path []byte) { |
|
||||||
it.path = path |
|
||||||
it.stack = append(it.stack, state) |
|
||||||
if parentIndex != nil { |
|
||||||
*parentIndex++ |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (it *nodeIterator) pop() { |
|
||||||
parent := it.stack[len(it.stack)-1] |
|
||||||
it.path = it.path[:parent.pathlen] |
|
||||||
it.stack = it.stack[:len(it.stack)-1] |
|
||||||
} |
|
||||||
|
|
||||||
func compareNodes(a, b NodeIterator) int { |
|
||||||
if cmp := bytes.Compare(a.Path(), b.Path()); cmp != 0 { |
|
||||||
return cmp |
|
||||||
} |
|
||||||
if a.Leaf() && !b.Leaf() { |
|
||||||
return -1 |
|
||||||
} else if b.Leaf() && !a.Leaf() { |
|
||||||
return 1 |
|
||||||
} |
|
||||||
if cmp := bytes.Compare(a.Hash().Bytes(), b.Hash().Bytes()); cmp != 0 { |
|
||||||
return cmp |
|
||||||
} |
|
||||||
if a.Leaf() && b.Leaf() { |
|
||||||
return bytes.Compare(a.LeafBlob(), b.LeafBlob()) |
|
||||||
} |
|
||||||
return 0 |
|
||||||
} |
|
||||||
|
|
||||||
type differenceIterator struct { |
|
||||||
a, b NodeIterator // Nodes returned are those in b - a.
|
|
||||||
eof bool // Indicates a has run out of elements
|
|
||||||
count int // Number of nodes scanned on either trie
|
|
||||||
} |
|
||||||
|
|
||||||
// NewDifferenceIterator constructs a NodeIterator that iterates over elements in b that
|
|
||||||
// are not in a. Returns the iterator, and a pointer to an integer recording the number
|
|
||||||
// of nodes seen.
|
|
||||||
func NewDifferenceIterator(a, b NodeIterator) (NodeIterator, *int) { |
|
||||||
a.Next(true) |
|
||||||
it := &differenceIterator{ |
|
||||||
a: a, |
|
||||||
b: b, |
|
||||||
} |
|
||||||
return it, &it.count |
|
||||||
} |
|
||||||
|
|
||||||
func (it *differenceIterator) Hash() common.Hash { |
|
||||||
return it.b.Hash() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *differenceIterator) Parent() common.Hash { |
|
||||||
return it.b.Parent() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *differenceIterator) Leaf() bool { |
|
||||||
return it.b.Leaf() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *differenceIterator) LeafKey() []byte { |
|
||||||
return it.b.LeafKey() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *differenceIterator) LeafBlob() []byte { |
|
||||||
return it.b.LeafBlob() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *differenceIterator) LeafProof() [][]byte { |
|
||||||
return it.b.LeafProof() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *differenceIterator) Path() []byte { |
|
||||||
return it.b.Path() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *differenceIterator) Next(bool) bool { |
|
||||||
// Invariants:
|
|
||||||
// - We always advance at least one element in b.
|
|
||||||
// - At the start of this function, a's path is lexically greater than b's.
|
|
||||||
if !it.b.Next(true) { |
|
||||||
return false |
|
||||||
} |
|
||||||
it.count++ |
|
||||||
|
|
||||||
if it.eof { |
|
||||||
// a has reached eof, so we just return all elements from b
|
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
for { |
|
||||||
switch compareNodes(it.a, it.b) { |
|
||||||
case -1: |
|
||||||
// b jumped past a; advance a
|
|
||||||
if !it.a.Next(true) { |
|
||||||
it.eof = true |
|
||||||
return true |
|
||||||
} |
|
||||||
it.count++ |
|
||||||
case 1: |
|
||||||
// b is before a
|
|
||||||
return true |
|
||||||
case 0: |
|
||||||
// a and b are identical; skip this whole subtree if the nodes have hashes
|
|
||||||
hasHash := it.a.Hash() == common.Hash{} |
|
||||||
if !it.b.Next(hasHash) { |
|
||||||
return false |
|
||||||
} |
|
||||||
it.count++ |
|
||||||
if !it.a.Next(hasHash) { |
|
||||||
it.eof = true |
|
||||||
return true |
|
||||||
} |
|
||||||
it.count++ |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (it *differenceIterator) Error() error { |
|
||||||
if err := it.a.Error(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return it.b.Error() |
|
||||||
} |
|
||||||
|
|
||||||
type nodeIteratorHeap []NodeIterator |
|
||||||
|
|
||||||
func (h nodeIteratorHeap) Len() int { return len(h) } |
|
||||||
func (h nodeIteratorHeap) Less(i, j int) bool { return compareNodes(h[i], h[j]) < 0 } |
|
||||||
func (h nodeIteratorHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } |
|
||||||
func (h *nodeIteratorHeap) Push(x interface{}) { *h = append(*h, x.(NodeIterator)) } |
|
||||||
func (h *nodeIteratorHeap) Pop() interface{} { |
|
||||||
n := len(*h) |
|
||||||
x := (*h)[n-1] |
|
||||||
*h = (*h)[0 : n-1] |
|
||||||
return x |
|
||||||
} |
|
||||||
|
|
||||||
type unionIterator struct { |
|
||||||
items *nodeIteratorHeap // Nodes returned are the union of the ones in these iterators
|
|
||||||
count int // Number of nodes scanned across all tries
|
|
||||||
} |
|
||||||
|
|
||||||
// NewUnionIterator constructs a NodeIterator that iterates over elements in the union
|
|
||||||
// of the provided NodeIterators. Returns the iterator, and a pointer to an integer
|
|
||||||
// recording the number of nodes visited.
|
|
||||||
func NewUnionIterator(iters []NodeIterator) (NodeIterator, *int) { |
|
||||||
h := make(nodeIteratorHeap, len(iters)) |
|
||||||
copy(h, iters) |
|
||||||
heap.Init(&h) |
|
||||||
|
|
||||||
ui := &unionIterator{items: &h} |
|
||||||
return ui, &ui.count |
|
||||||
} |
|
||||||
|
|
||||||
func (it *unionIterator) Hash() common.Hash { |
|
||||||
return (*it.items)[0].Hash() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *unionIterator) Parent() common.Hash { |
|
||||||
return (*it.items)[0].Parent() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *unionIterator) Leaf() bool { |
|
||||||
return (*it.items)[0].Leaf() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *unionIterator) LeafKey() []byte { |
|
||||||
return (*it.items)[0].LeafKey() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *unionIterator) LeafBlob() []byte { |
|
||||||
return (*it.items)[0].LeafBlob() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *unionIterator) LeafProof() [][]byte { |
|
||||||
return (*it.items)[0].LeafProof() |
|
||||||
} |
|
||||||
|
|
||||||
func (it *unionIterator) Path() []byte { |
|
||||||
return (*it.items)[0].Path() |
|
||||||
} |
|
||||||
|
|
||||||
// Next returns the next node in the union of tries being iterated over.
|
|
||||||
//
|
|
||||||
// It does this by maintaining a heap of iterators, sorted by the iteration
|
|
||||||
// order of their next elements, with one entry for each source trie. Each
|
|
||||||
// time Next() is called, it takes the least element from the heap to return,
|
|
||||||
// advancing any other iterators that also point to that same element. These
|
|
||||||
// iterators are called with descend=false, since we know that any nodes under
|
|
||||||
// these nodes will also be duplicates, found in the currently selected iterator.
|
|
||||||
// Whenever an iterator is advanced, it is pushed back into the heap if it still
|
|
||||||
// has elements remaining.
|
|
||||||
//
|
|
||||||
// In the case that descend=false - eg, we're asked to ignore all subnodes of the
|
|
||||||
// current node - we also advance any iterators in the heap that have the current
|
|
||||||
// path as a prefix.
|
|
||||||
func (it *unionIterator) Next(descend bool) bool { |
|
||||||
if len(*it.items) == 0 { |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// Get the next key from the union
|
|
||||||
least := heap.Pop(it.items).(NodeIterator) |
|
||||||
|
|
||||||
// Skip over other nodes as long as they're identical, or, if we're not descending, as
|
|
||||||
// long as they have the same prefix as the current node.
|
|
||||||
for len(*it.items) > 0 && ((!descend && bytes.HasPrefix((*it.items)[0].Path(), least.Path())) || compareNodes(least, (*it.items)[0]) == 0) { |
|
||||||
skipped := heap.Pop(it.items).(NodeIterator) |
|
||||||
// Skip the whole subtree if the nodes have hashes; otherwise just skip this node
|
|
||||||
if skipped.Next(skipped.Hash() == common.Hash{}) { |
|
||||||
it.count++ |
|
||||||
// If there are more elements, push the iterator back on the heap
|
|
||||||
heap.Push(it.items, skipped) |
|
||||||
} |
|
||||||
} |
|
||||||
if least.Next(descend) { |
|
||||||
it.count++ |
|
||||||
heap.Push(it.items, least) |
|
||||||
} |
|
||||||
return len(*it.items) > 0 |
|
||||||
} |
|
||||||
|
|
||||||
func (it *unionIterator) Error() error { |
|
||||||
for i := 0; i < len(*it.items); i++ { |
|
||||||
if err := (*it.items)[i].Error(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
@ -1,435 +0,0 @@ |
|||||||
// Copyright 2014 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"fmt" |
|
||||||
"math/rand" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
hdb "github.com/harmony-one/harmony/internal/db" |
|
||||||
) |
|
||||||
|
|
||||||
func TestIterator(t *testing.T) { |
|
||||||
trie := newEmpty() |
|
||||||
vals := []struct{ k, v string }{ |
|
||||||
{"do", "verb"}, |
|
||||||
{"ether", "wookiedoo"}, |
|
||||||
{"horse", "stallion"}, |
|
||||||
{"shaman", "horse"}, |
|
||||||
{"doge", "coin"}, |
|
||||||
{"dog", "puppy"}, |
|
||||||
{"somethingveryoddindeedthis is", "myothernodedata"}, |
|
||||||
} |
|
||||||
all := make(map[string]string) |
|
||||||
for _, val := range vals { |
|
||||||
all[val.k] = val.v |
|
||||||
trie.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} |
|
||||||
trie.Commit(nil) |
|
||||||
|
|
||||||
found := make(map[string]string) |
|
||||||
it := NewIterator(trie.NodeIterator(nil)) |
|
||||||
for it.Next() { |
|
||||||
found[string(it.Key)] = string(it.Value) |
|
||||||
} |
|
||||||
|
|
||||||
for k, v := range all { |
|
||||||
if found[k] != v { |
|
||||||
t.Errorf("iterator value mismatch for %s: got %q want %q", k, found[k], v) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
type kv struct { |
|
||||||
k, v []byte |
|
||||||
t bool |
|
||||||
} |
|
||||||
|
|
||||||
func TestIteratorLargeData(t *testing.T) { |
|
||||||
trie := newEmpty() |
|
||||||
vals := make(map[string]*kv) |
|
||||||
|
|
||||||
for i := byte(0); i < 255; i++ { |
|
||||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} |
|
||||||
value2 := &kv{common.LeftPadBytes([]byte{10, i}, 32), []byte{i}, false} |
|
||||||
trie.Update(value.k, value.v) |
|
||||||
trie.Update(value2.k, value2.v) |
|
||||||
vals[string(value.k)] = value |
|
||||||
vals[string(value2.k)] = value2 |
|
||||||
} |
|
||||||
|
|
||||||
it := NewIterator(trie.NodeIterator(nil)) |
|
||||||
for it.Next() { |
|
||||||
vals[string(it.Key)].t = true |
|
||||||
} |
|
||||||
|
|
||||||
var untouched []*kv |
|
||||||
for _, value := range vals { |
|
||||||
if !value.t { |
|
||||||
untouched = append(untouched, value) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if len(untouched) > 0 { |
|
||||||
t.Errorf("Missed %d nodes", len(untouched)) |
|
||||||
for _, value := range untouched { |
|
||||||
t.Error(value) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Tests that the node iterator indeed walks over the entire database contents.
|
|
||||||
func TestNodeIteratorCoverage(t *testing.T) { |
|
||||||
// Create some arbitrary test trie to iterate
|
|
||||||
db, trie, _ := makeTestTrie() |
|
||||||
|
|
||||||
// Gather all the node hashes found by the iterator
|
|
||||||
hashes := make(map[common.Hash]struct{}) |
|
||||||
for it := trie.NodeIterator(nil); it.Next(true); { |
|
||||||
if it.Hash() != (common.Hash{}) { |
|
||||||
hashes[it.Hash()] = struct{}{} |
|
||||||
} |
|
||||||
} |
|
||||||
// Cross check the hashes and the database itself
|
|
||||||
for hash := range hashes { |
|
||||||
if _, err := db.Node(hash); err != nil { |
|
||||||
t.Errorf("failed to retrieve reported node %x: %v", hash, err) |
|
||||||
} |
|
||||||
} |
|
||||||
for hash, obj := range db.nodes { |
|
||||||
if obj != nil && hash != (common.Hash{}) { |
|
||||||
if _, ok := hashes[hash]; !ok { |
|
||||||
t.Errorf("state entry not reported %x", hash) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
for _, key := range db.diskdb.(*hdb.MemDatabase).Keys() { |
|
||||||
if _, ok := hashes[common.BytesToHash(key)]; !ok { |
|
||||||
t.Errorf("state entry not reported %x", key) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
type kvs struct{ k, v string } |
|
||||||
|
|
||||||
var testdata1 = []kvs{ |
|
||||||
{"barb", "ba"}, |
|
||||||
{"bard", "bc"}, |
|
||||||
{"bars", "bb"}, |
|
||||||
{"bar", "b"}, |
|
||||||
{"fab", "z"}, |
|
||||||
{"food", "ab"}, |
|
||||||
{"foos", "aa"}, |
|
||||||
{"foo", "a"}, |
|
||||||
} |
|
||||||
|
|
||||||
var testdata2 = []kvs{ |
|
||||||
{"aardvark", "c"}, |
|
||||||
{"bar", "b"}, |
|
||||||
{"barb", "bd"}, |
|
||||||
{"bars", "be"}, |
|
||||||
{"fab", "z"}, |
|
||||||
{"foo", "a"}, |
|
||||||
{"foos", "aa"}, |
|
||||||
{"food", "ab"}, |
|
||||||
{"jars", "d"}, |
|
||||||
} |
|
||||||
|
|
||||||
func TestIteratorSeek(t *testing.T) { |
|
||||||
trie := newEmpty() |
|
||||||
for _, val := range testdata1 { |
|
||||||
trie.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} |
|
||||||
|
|
||||||
// Seek to the middle.
|
|
||||||
it := NewIterator(trie.NodeIterator([]byte("fab"))) |
|
||||||
if err := checkIteratorOrder(testdata1[4:], it); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
// Seek to a non-existent key.
|
|
||||||
it = NewIterator(trie.NodeIterator([]byte("barc"))) |
|
||||||
if err := checkIteratorOrder(testdata1[1:], it); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
|
|
||||||
// Seek beyond the end.
|
|
||||||
it = NewIterator(trie.NodeIterator([]byte("z"))) |
|
||||||
if err := checkIteratorOrder(nil, it); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func checkIteratorOrder(want []kvs, it *Iterator) error { |
|
||||||
for it.Next() { |
|
||||||
if len(want) == 0 { |
|
||||||
return fmt.Errorf("didn't expect any more values, got key %q", it.Key) |
|
||||||
} |
|
||||||
if !bytes.Equal(it.Key, []byte(want[0].k)) { |
|
||||||
return fmt.Errorf("wrong key: got %q, want %q", it.Key, want[0].k) |
|
||||||
} |
|
||||||
want = want[1:] |
|
||||||
} |
|
||||||
if len(want) > 0 { |
|
||||||
return fmt.Errorf("iterator ended early, want key %q", want[0]) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func TestDifferenceIterator(t *testing.T) { |
|
||||||
triea := newEmpty() |
|
||||||
for _, val := range testdata1 { |
|
||||||
triea.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} |
|
||||||
triea.Commit(nil) |
|
||||||
|
|
||||||
trieb := newEmpty() |
|
||||||
for _, val := range testdata2 { |
|
||||||
trieb.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} |
|
||||||
trieb.Commit(nil) |
|
||||||
|
|
||||||
found := make(map[string]string) |
|
||||||
di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil)) |
|
||||||
it := NewIterator(di) |
|
||||||
for it.Next() { |
|
||||||
found[string(it.Key)] = string(it.Value) |
|
||||||
} |
|
||||||
|
|
||||||
all := []struct{ k, v string }{ |
|
||||||
{"aardvark", "c"}, |
|
||||||
{"barb", "bd"}, |
|
||||||
{"bars", "be"}, |
|
||||||
{"jars", "d"}, |
|
||||||
} |
|
||||||
for _, item := range all { |
|
||||||
if found[item.k] != item.v { |
|
||||||
t.Errorf("iterator value mismatch for %s: got %v want %v", item.k, found[item.k], item.v) |
|
||||||
} |
|
||||||
} |
|
||||||
if len(found) != len(all) { |
|
||||||
t.Errorf("iterator count mismatch: got %d values, want %d", len(found), len(all)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestUnionIterator(t *testing.T) { |
|
||||||
triea := newEmpty() |
|
||||||
for _, val := range testdata1 { |
|
||||||
triea.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} |
|
||||||
triea.Commit(nil) |
|
||||||
|
|
||||||
trieb := newEmpty() |
|
||||||
for _, val := range testdata2 { |
|
||||||
trieb.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} |
|
||||||
trieb.Commit(nil) |
|
||||||
|
|
||||||
di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)}) |
|
||||||
it := NewIterator(di) |
|
||||||
|
|
||||||
all := []struct{ k, v string }{ |
|
||||||
{"aardvark", "c"}, |
|
||||||
{"barb", "ba"}, |
|
||||||
{"barb", "bd"}, |
|
||||||
{"bard", "bc"}, |
|
||||||
{"bars", "bb"}, |
|
||||||
{"bars", "be"}, |
|
||||||
{"bar", "b"}, |
|
||||||
{"fab", "z"}, |
|
||||||
{"food", "ab"}, |
|
||||||
{"foos", "aa"}, |
|
||||||
{"foo", "a"}, |
|
||||||
{"jars", "d"}, |
|
||||||
} |
|
||||||
|
|
||||||
for i, kv := range all { |
|
||||||
if !it.Next() { |
|
||||||
t.Errorf("Iterator ends prematurely at element %d", i) |
|
||||||
} |
|
||||||
if kv.k != string(it.Key) { |
|
||||||
t.Errorf("iterator value mismatch for element %d: got key %s want %s", i, it.Key, kv.k) |
|
||||||
} |
|
||||||
if kv.v != string(it.Value) { |
|
||||||
t.Errorf("iterator value mismatch for element %d: got value %s want %s", i, it.Value, kv.v) |
|
||||||
} |
|
||||||
} |
|
||||||
if it.Next() { |
|
||||||
t.Errorf("Iterator returned extra values.") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestIteratorNoDups(t *testing.T) { |
|
||||||
var tr Trie |
|
||||||
for _, val := range testdata1 { |
|
||||||
tr.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} |
|
||||||
checkIteratorNoDups(t, tr.NodeIterator(nil), nil) |
|
||||||
} |
|
||||||
|
|
||||||
// This test checks that nodeIterator.Next can be retried after inserting missing trie nodes.
|
|
||||||
func TestIteratorContinueAfterErrorDisk(t *testing.T) { testIteratorContinueAfterError(t, false) } |
|
||||||
func TestIteratorContinueAfterErrorMemonly(t *testing.T) { testIteratorContinueAfterError(t, true) } |
|
||||||
|
|
||||||
func testIteratorContinueAfterError(t *testing.T, memonly bool) { |
|
||||||
diskdb := hdb.NewMemDatabase() |
|
||||||
triedb := NewDatabase(diskdb) |
|
||||||
|
|
||||||
tr, _ := New(common.Hash{}, triedb) |
|
||||||
for _, val := range testdata1 { |
|
||||||
tr.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} |
|
||||||
tr.Commit(nil) |
|
||||||
if !memonly { |
|
||||||
triedb.Commit(tr.Hash(), true) |
|
||||||
} |
|
||||||
wantNodeCount := checkIteratorNoDups(t, tr.NodeIterator(nil), nil) |
|
||||||
|
|
||||||
var ( |
|
||||||
diskKeys [][]byte |
|
||||||
memKeys []common.Hash |
|
||||||
) |
|
||||||
if memonly { |
|
||||||
memKeys = triedb.Nodes() |
|
||||||
} else { |
|
||||||
diskKeys = diskdb.Keys() |
|
||||||
} |
|
||||||
for i := 0; i < 20; i++ { |
|
||||||
// Create trie that will load all nodes from DB.
|
|
||||||
tr, _ := New(tr.Hash(), triedb) |
|
||||||
|
|
||||||
// Remove a random node from the database. It can't be the root node
|
|
||||||
// because that one is already loaded.
|
|
||||||
var ( |
|
||||||
rkey common.Hash |
|
||||||
rval []byte |
|
||||||
robj *cachedNode |
|
||||||
) |
|
||||||
for { |
|
||||||
if memonly { |
|
||||||
rkey = memKeys[rand.Intn(len(memKeys))] |
|
||||||
} else { |
|
||||||
copy(rkey[:], diskKeys[rand.Intn(len(diskKeys))]) |
|
||||||
} |
|
||||||
if rkey != tr.Hash() { |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
if memonly { |
|
||||||
robj = triedb.nodes[rkey] |
|
||||||
delete(triedb.nodes, rkey) |
|
||||||
} else { |
|
||||||
rval, _ = diskdb.Get(rkey[:]) |
|
||||||
diskdb.Delete(rkey[:]) |
|
||||||
} |
|
||||||
// Iterate until the error is hit.
|
|
||||||
seen := make(map[string]bool) |
|
||||||
it := tr.NodeIterator(nil) |
|
||||||
checkIteratorNoDups(t, it, seen) |
|
||||||
missing, ok := it.Error().(*MissingNodeError) |
|
||||||
if !ok || missing.NodeHash != rkey { |
|
||||||
t.Fatal("didn't hit missing node, got", it.Error()) |
|
||||||
} |
|
||||||
|
|
||||||
// Add the node back and continue iteration.
|
|
||||||
if memonly { |
|
||||||
triedb.nodes[rkey] = robj |
|
||||||
} else { |
|
||||||
diskdb.Put(rkey[:], rval) |
|
||||||
} |
|
||||||
checkIteratorNoDups(t, it, seen) |
|
||||||
if it.Error() != nil { |
|
||||||
t.Fatal("unexpected error", it.Error()) |
|
||||||
} |
|
||||||
if len(seen) != wantNodeCount { |
|
||||||
t.Fatal("wrong node iteration count, got", len(seen), "want", wantNodeCount) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Similar to the test above, this one checks that failure to create nodeIterator at a
|
|
||||||
// certain key prefix behaves correctly when Next is called. The expectation is that Next
|
|
||||||
// should retry seeking before returning true for the first time.
|
|
||||||
func TestIteratorContinueAfterSeekErrorDisk(t *testing.T) { |
|
||||||
testIteratorContinueAfterSeekError(t, false) |
|
||||||
} |
|
||||||
func TestIteratorContinueAfterSeekErrorMemonly(t *testing.T) { |
|
||||||
testIteratorContinueAfterSeekError(t, true) |
|
||||||
} |
|
||||||
|
|
||||||
func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { |
|
||||||
// Commit test trie to db, then remove the node containing "bars".
|
|
||||||
diskdb := hdb.NewMemDatabase() |
|
||||||
triedb := NewDatabase(diskdb) |
|
||||||
|
|
||||||
ctr, _ := New(common.Hash{}, triedb) |
|
||||||
for _, val := range testdata1 { |
|
||||||
ctr.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} |
|
||||||
root, _ := ctr.Commit(nil) |
|
||||||
if !memonly { |
|
||||||
triedb.Commit(root, true) |
|
||||||
} |
|
||||||
barNodeHash := common.HexToHash("05041990364eb72fcb1127652ce40d8bab765f2bfe53225b1170d276cc101c2e") |
|
||||||
var ( |
|
||||||
barNodeBlob []byte |
|
||||||
barNodeObj *cachedNode |
|
||||||
) |
|
||||||
if memonly { |
|
||||||
barNodeObj = triedb.nodes[barNodeHash] |
|
||||||
delete(triedb.nodes, barNodeHash) |
|
||||||
} else { |
|
||||||
barNodeBlob, _ = diskdb.Get(barNodeHash[:]) |
|
||||||
diskdb.Delete(barNodeHash[:]) |
|
||||||
} |
|
||||||
// Create a new iterator that seeks to "bars". Seeking can't proceed because
|
|
||||||
// the node is missing.
|
|
||||||
tr, _ := New(root, triedb) |
|
||||||
it := tr.NodeIterator([]byte("bars")) |
|
||||||
missing, ok := it.Error().(*MissingNodeError) |
|
||||||
if !ok { |
|
||||||
t.Fatal("want MissingNodeError, got", it.Error()) |
|
||||||
} else if missing.NodeHash != barNodeHash { |
|
||||||
t.Fatal("wrong node missing") |
|
||||||
} |
|
||||||
// Reinsert the missing node.
|
|
||||||
if memonly { |
|
||||||
triedb.nodes[barNodeHash] = barNodeObj |
|
||||||
} else { |
|
||||||
diskdb.Put(barNodeHash[:], barNodeBlob) |
|
||||||
} |
|
||||||
// Check that iteration produces the right set of values.
|
|
||||||
if err := checkIteratorOrder(testdata1[2:], NewIterator(it)); err != nil { |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func checkIteratorNoDups(t *testing.T, it NodeIterator, seen map[string]bool) int { |
|
||||||
if seen == nil { |
|
||||||
seen = make(map[string]bool) |
|
||||||
} |
|
||||||
for it.Next(true) { |
|
||||||
if seen[string(it.Path())] { |
|
||||||
t.Fatalf("iterator visited node path %x twice", it.Path()) |
|
||||||
} |
|
||||||
seen[string(it.Path())] = true |
|
||||||
} |
|
||||||
return len(seen) |
|
||||||
} |
|
@ -1,237 +0,0 @@ |
|||||||
// Copyright 2014 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/rlp" |
|
||||||
) |
|
||||||
|
|
||||||
var indices = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "[17]"} |
|
||||||
|
|
||||||
type node interface { |
|
||||||
fstring(string) string |
|
||||||
cache() (hashNode, bool) |
|
||||||
canUnload(cachegen, cachelimit uint16) bool |
|
||||||
} |
|
||||||
|
|
||||||
type ( |
|
||||||
fullNode struct { |
|
||||||
Children [17]node // Actual trie node data to encode/decode (needs custom encoder)
|
|
||||||
flags nodeFlag |
|
||||||
} |
|
||||||
shortNode struct { |
|
||||||
Key []byte |
|
||||||
Val node |
|
||||||
flags nodeFlag |
|
||||||
} |
|
||||||
hashNode []byte |
|
||||||
valueNode []byte |
|
||||||
) |
|
||||||
|
|
||||||
// nilValueNode is used when collapsing internal trie nodes for hashing, since
|
|
||||||
// unset children need to serialize correctly.
|
|
||||||
var nilValueNode = valueNode(nil) |
|
||||||
|
|
||||||
// EncodeRLP encodes a full node into the consensus RLP format.
|
|
||||||
func (n *fullNode) EncodeRLP(w io.Writer) error { |
|
||||||
var nodes [17]node |
|
||||||
|
|
||||||
for i, child := range &n.Children { |
|
||||||
if child != nil { |
|
||||||
nodes[i] = child |
|
||||||
} else { |
|
||||||
nodes[i] = nilValueNode |
|
||||||
} |
|
||||||
} |
|
||||||
return rlp.Encode(w, nodes) |
|
||||||
} |
|
||||||
|
|
||||||
func (n *fullNode) copy() *fullNode { copy := *n; return © } |
|
||||||
func (n *shortNode) copy() *shortNode { copy := *n; return © } |
|
||||||
|
|
||||||
// nodeFlag contains caching-related metadata about a node.
|
|
||||||
type nodeFlag struct { |
|
||||||
hash hashNode // cached hash of the node (may be nil)
|
|
||||||
gen uint16 // cache generation counter
|
|
||||||
dirty bool // whether the node has changes that must be written to the database
|
|
||||||
} |
|
||||||
|
|
||||||
// canUnload tells whether a node can be unloaded.
|
|
||||||
func (n *nodeFlag) canUnload(cachegen, cachelimit uint16) bool { |
|
||||||
return !n.dirty && cachegen-n.gen >= cachelimit |
|
||||||
} |
|
||||||
|
|
||||||
func (n *fullNode) canUnload(gen, limit uint16) bool { return n.flags.canUnload(gen, limit) } |
|
||||||
func (n *shortNode) canUnload(gen, limit uint16) bool { return n.flags.canUnload(gen, limit) } |
|
||||||
func (n hashNode) canUnload(uint16, uint16) bool { return false } |
|
||||||
func (n valueNode) canUnload(uint16, uint16) bool { return false } |
|
||||||
|
|
||||||
func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } |
|
||||||
func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } |
|
||||||
func (n hashNode) cache() (hashNode, bool) { return nil, true } |
|
||||||
func (n valueNode) cache() (hashNode, bool) { return nil, true } |
|
||||||
|
|
||||||
// Pretty printing.
|
|
||||||
func (n *fullNode) String() string { return n.fstring("") } |
|
||||||
func (n *shortNode) String() string { return n.fstring("") } |
|
||||||
func (n hashNode) String() string { return n.fstring("") } |
|
||||||
func (n valueNode) String() string { return n.fstring("") } |
|
||||||
|
|
||||||
func (n *fullNode) fstring(ind string) string { |
|
||||||
resp := fmt.Sprintf("[\n%s ", ind) |
|
||||||
for i, node := range &n.Children { |
|
||||||
if node == nil { |
|
||||||
resp += fmt.Sprintf("%s: <nil> ", indices[i]) |
|
||||||
} else { |
|
||||||
resp += fmt.Sprintf("%s: %v", indices[i], node.fstring(ind+" ")) |
|
||||||
} |
|
||||||
} |
|
||||||
return resp + fmt.Sprintf("\n%s] ", ind) |
|
||||||
} |
|
||||||
func (n *shortNode) fstring(ind string) string { |
|
||||||
return fmt.Sprintf("{%x: %v} ", n.Key, n.Val.fstring(ind+" ")) |
|
||||||
} |
|
||||||
func (n hashNode) fstring(ind string) string { |
|
||||||
return fmt.Sprintf("<%x> ", []byte(n)) |
|
||||||
} |
|
||||||
func (n valueNode) fstring(ind string) string { |
|
||||||
return fmt.Sprintf("%x ", []byte(n)) |
|
||||||
} |
|
||||||
|
|
||||||
func mustDecodeNode(hash, buf []byte, cachegen uint16) node { |
|
||||||
n, err := decodeNode(hash, buf, cachegen) |
|
||||||
if err != nil { |
|
||||||
panic(fmt.Sprintf("node %x: %v", hash, err)) |
|
||||||
} |
|
||||||
return n |
|
||||||
} |
|
||||||
|
|
||||||
// decodeNode parses the RLP encoding of a trie node.
|
|
||||||
func decodeNode(hash, buf []byte, cachegen uint16) (node, error) { |
|
||||||
if len(buf) == 0 { |
|
||||||
return nil, io.ErrUnexpectedEOF |
|
||||||
} |
|
||||||
elems, _, err := rlp.SplitList(buf) |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("decode error: %v", err) |
|
||||||
} |
|
||||||
switch c, _ := rlp.CountValues(elems); c { |
|
||||||
case 2: |
|
||||||
n, err := decodeShort(hash, elems, cachegen) |
|
||||||
return n, wrapError(err, "short") |
|
||||||
case 17: |
|
||||||
n, err := decodeFull(hash, elems, cachegen) |
|
||||||
return n, wrapError(err, "full") |
|
||||||
default: |
|
||||||
return nil, fmt.Errorf("invalid number of list elements: %v", c) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func decodeShort(hash, elems []byte, cachegen uint16) (node, error) { |
|
||||||
kbuf, rest, err := rlp.SplitString(elems) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
flag := nodeFlag{hash: hash, gen: cachegen} |
|
||||||
key := compactToHex(kbuf) |
|
||||||
if hasTerm(key) { |
|
||||||
// value node
|
|
||||||
val, _, err := rlp.SplitString(rest) |
|
||||||
if err != nil { |
|
||||||
return nil, fmt.Errorf("invalid value node: %v", err) |
|
||||||
} |
|
||||||
return &shortNode{key, append(valueNode{}, val...), flag}, nil |
|
||||||
} |
|
||||||
r, _, err := decodeRef(rest, cachegen) |
|
||||||
if err != nil { |
|
||||||
return nil, wrapError(err, "val") |
|
||||||
} |
|
||||||
return &shortNode{key, r, flag}, nil |
|
||||||
} |
|
||||||
|
|
||||||
func decodeFull(hash, elems []byte, cachegen uint16) (*fullNode, error) { |
|
||||||
n := &fullNode{flags: nodeFlag{hash: hash, gen: cachegen}} |
|
||||||
for i := 0; i < 16; i++ { |
|
||||||
cld, rest, err := decodeRef(elems, cachegen) |
|
||||||
if err != nil { |
|
||||||
return n, wrapError(err, fmt.Sprintf("[%d]", i)) |
|
||||||
} |
|
||||||
n.Children[i], elems = cld, rest |
|
||||||
} |
|
||||||
val, _, err := rlp.SplitString(elems) |
|
||||||
if err != nil { |
|
||||||
return n, err |
|
||||||
} |
|
||||||
if len(val) > 0 { |
|
||||||
n.Children[16] = append(valueNode{}, val...) |
|
||||||
} |
|
||||||
return n, nil |
|
||||||
} |
|
||||||
|
|
||||||
const hashLen = len(common.Hash{}) |
|
||||||
|
|
||||||
func decodeRef(buf []byte, cachegen uint16) (node, []byte, error) { |
|
||||||
kind, val, rest, err := rlp.Split(buf) |
|
||||||
if err != nil { |
|
||||||
return nil, buf, err |
|
||||||
} |
|
||||||
switch { |
|
||||||
case kind == rlp.List: |
|
||||||
// 'embedded' node reference. The encoding must be smaller
|
|
||||||
// than a hash in order to be valid.
|
|
||||||
if size := len(buf) - len(rest); size > hashLen { |
|
||||||
err := fmt.Errorf("oversized embedded node (size is %d bytes, want size < %d)", size, hashLen) |
|
||||||
return nil, buf, err |
|
||||||
} |
|
||||||
n, err := decodeNode(nil, buf, cachegen) |
|
||||||
return n, rest, err |
|
||||||
case kind == rlp.String && len(val) == 0: |
|
||||||
// empty node
|
|
||||||
return nil, rest, nil |
|
||||||
case kind == rlp.String && len(val) == 32: |
|
||||||
return append(hashNode{}, val...), rest, nil |
|
||||||
default: |
|
||||||
return nil, nil, fmt.Errorf("invalid RLP string size %d (want 0 or 32)", len(val)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// wraps a decoding error with information about the path to the
|
|
||||||
// invalid child node (for debugging encoding issues).
|
|
||||||
type decodeError struct { |
|
||||||
what error |
|
||||||
stack []string |
|
||||||
} |
|
||||||
|
|
||||||
func wrapError(err error, ctx string) error { |
|
||||||
if err == nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
if decErr, ok := err.(*decodeError); ok { |
|
||||||
decErr.stack = append(decErr.stack, ctx) |
|
||||||
return decErr |
|
||||||
} |
|
||||||
return &decodeError{err, []string{ctx}} |
|
||||||
} |
|
||||||
|
|
||||||
func (err *decodeError) Error() string { |
|
||||||
return fmt.Sprintf("%v (decode path: %s)", err.what, strings.Join(err.stack, "<-")) |
|
||||||
} |
|
@ -1,58 +0,0 @@ |
|||||||
// Copyright 2016 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 trie |
|
||||||
|
|
||||||
import "testing" |
|
||||||
|
|
||||||
func TestCanUnload(t *testing.T) { |
|
||||||
tests := []struct { |
|
||||||
flag nodeFlag |
|
||||||
cachegen, cachelimit uint16 |
|
||||||
want bool |
|
||||||
}{ |
|
||||||
{ |
|
||||||
flag: nodeFlag{dirty: true, gen: 0}, |
|
||||||
want: false, |
|
||||||
}, |
|
||||||
{ |
|
||||||
flag: nodeFlag{dirty: false, gen: 0}, |
|
||||||
cachegen: 0, cachelimit: 0, |
|
||||||
want: true, |
|
||||||
}, |
|
||||||
{ |
|
||||||
flag: nodeFlag{dirty: false, gen: 65534}, |
|
||||||
cachegen: 65535, cachelimit: 1, |
|
||||||
want: true, |
|
||||||
}, |
|
||||||
{ |
|
||||||
flag: nodeFlag{dirty: false, gen: 65534}, |
|
||||||
cachegen: 0, cachelimit: 1, |
|
||||||
want: true, |
|
||||||
}, |
|
||||||
{ |
|
||||||
flag: nodeFlag{dirty: false, gen: 1}, |
|
||||||
cachegen: 65535, cachelimit: 1, |
|
||||||
want: true, |
|
||||||
}, |
|
||||||
} |
|
||||||
|
|
||||||
for _, test := range tests { |
|
||||||
if got := test.flag.canUnload(test.cachegen, test.cachelimit); got != test.want { |
|
||||||
t.Errorf("%+v\n got %t, want %t", test, got, test.want) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,153 +0,0 @@ |
|||||||
// Copyright 2015 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"fmt" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/crypto" |
|
||||||
"github.com/ethereum/go-ethereum/log" |
|
||||||
"github.com/ethereum/go-ethereum/rlp" |
|
||||||
"github.com/harmony-one/harmony/internal/db" |
|
||||||
) |
|
||||||
|
|
||||||
// Prove constructs a merkle proof for key. The result contains all encoded nodes
|
|
||||||
// on the path to the value at key. The value itself is also included in the last
|
|
||||||
// node and can be retrieved by verifying the proof.
|
|
||||||
//
|
|
||||||
// If the trie does not contain a value for key, the returned proof contains all
|
|
||||||
// nodes of the longest existing prefix of the key (at least the root node), ending
|
|
||||||
// with the node that proves the absence of the key.
|
|
||||||
func (t *Trie) Prove(key []byte, fromLevel uint, proofDb db.Putter) error { |
|
||||||
// Collect all nodes on the path to key.
|
|
||||||
key = keybytesToHex(key) |
|
||||||
nodes := []node{} |
|
||||||
tn := t.root |
|
||||||
for len(key) > 0 && tn != nil { |
|
||||||
switch n := tn.(type) { |
|
||||||
case *shortNode: |
|
||||||
if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { |
|
||||||
// The trie doesn't contain the key.
|
|
||||||
tn = nil |
|
||||||
} else { |
|
||||||
tn = n.Val |
|
||||||
key = key[len(n.Key):] |
|
||||||
} |
|
||||||
nodes = append(nodes, n) |
|
||||||
case *fullNode: |
|
||||||
tn = n.Children[key[0]] |
|
||||||
key = key[1:] |
|
||||||
nodes = append(nodes, n) |
|
||||||
case hashNode: |
|
||||||
var err error |
|
||||||
tn, err = t.resolveHash(n, nil) |
|
||||||
if err != nil { |
|
||||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) |
|
||||||
return err |
|
||||||
} |
|
||||||
default: |
|
||||||
panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) |
|
||||||
} |
|
||||||
} |
|
||||||
hasher := newHasher(0, 0, nil) |
|
||||||
for i, n := range nodes { |
|
||||||
// Don't bother checking for errors here since hasher panics
|
|
||||||
// if encoding doesn't work and we're not writing to any database.
|
|
||||||
n, _, _ = hasher.hashChildren(n, nil) |
|
||||||
hn, _ := hasher.store(n, nil, false) |
|
||||||
if hash, ok := hn.(hashNode); ok || i == 0 { |
|
||||||
// If the node's database encoding is a hash (or is the
|
|
||||||
// root node), it becomes a proof element.
|
|
||||||
if fromLevel > 0 { |
|
||||||
fromLevel-- |
|
||||||
} else { |
|
||||||
enc, _ := rlp.EncodeToBytes(n) |
|
||||||
if !ok { |
|
||||||
hash = crypto.Keccak256(enc) |
|
||||||
} |
|
||||||
proofDb.Put(hash, enc) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Prove constructs a merkle proof for key. The result contains all encoded nodes
|
|
||||||
// on the path to the value at key. The value itself is also included in the last
|
|
||||||
// node and can be retrieved by verifying the proof.
|
|
||||||
//
|
|
||||||
// If the trie does not contain a value for key, the returned proof contains all
|
|
||||||
// nodes of the longest existing prefix of the key (at least the root node), ending
|
|
||||||
// with the node that proves the absence of the key.
|
|
||||||
func (t *SecureTrie) Prove(key []byte, fromLevel uint, proofDb db.Putter) error { |
|
||||||
return t.trie.Prove(key, fromLevel, proofDb) |
|
||||||
} |
|
||||||
|
|
||||||
// VerifyProof checks merkle proofs. The given proof must contain the value for
|
|
||||||
// key in a trie with the given root hash. VerifyProof returns an error if the
|
|
||||||
// proof contains invalid trie nodes or the wrong value.
|
|
||||||
func VerifyProof(rootHash common.Hash, key []byte, proofDb DatabaseReader) (value []byte, nodes int, err error) { |
|
||||||
key = keybytesToHex(key) |
|
||||||
wantHash := rootHash |
|
||||||
for i := 0; ; i++ { |
|
||||||
buf, _ := proofDb.Get(wantHash[:]) |
|
||||||
if buf == nil { |
|
||||||
return nil, i, fmt.Errorf("proof node %d (hash %064x) missing", i, wantHash) |
|
||||||
} |
|
||||||
n, err := decodeNode(wantHash[:], buf, 0) |
|
||||||
if err != nil { |
|
||||||
return nil, i, fmt.Errorf("bad proof node %d: %v", i, err) |
|
||||||
} |
|
||||||
keyrest, cld := get(n, key) |
|
||||||
switch cld := cld.(type) { |
|
||||||
case nil: |
|
||||||
// The trie doesn't contain the key.
|
|
||||||
return nil, i, nil |
|
||||||
case hashNode: |
|
||||||
key = keyrest |
|
||||||
copy(wantHash[:], cld) |
|
||||||
case valueNode: |
|
||||||
return cld, i + 1, nil |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func get(tn node, key []byte) ([]byte, node) { |
|
||||||
for { |
|
||||||
switch n := tn.(type) { |
|
||||||
case *shortNode: |
|
||||||
if len(key) < len(n.Key) || !bytes.Equal(n.Key, key[:len(n.Key)]) { |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
tn = n.Val |
|
||||||
key = key[len(n.Key):] |
|
||||||
case *fullNode: |
|
||||||
tn = n.Children[key[0]] |
|
||||||
key = key[1:] |
|
||||||
case hashNode: |
|
||||||
return key, n |
|
||||||
case nil: |
|
||||||
return key, nil |
|
||||||
case valueNode: |
|
||||||
return nil, n |
|
||||||
default: |
|
||||||
panic(fmt.Sprintf("%T: invalid node: %v", tn, tn)) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,218 +0,0 @@ |
|||||||
// Copyright 2015 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
crand "crypto/rand" |
|
||||||
mrand "math/rand" |
|
||||||
"testing" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/crypto" |
|
||||||
"github.com/ethereum/go-ethereum/ethdb" |
|
||||||
) |
|
||||||
|
|
||||||
func init() { |
|
||||||
mrand.Seed(time.Now().Unix()) |
|
||||||
} |
|
||||||
|
|
||||||
// makeProvers creates Merkle trie provers based on different implementations to
|
|
||||||
// test all variations.
|
|
||||||
func makeProvers(trie *Trie) []func(key []byte) *ethdb.MemDatabase { |
|
||||||
var provers []func(key []byte) *ethdb.MemDatabase |
|
||||||
|
|
||||||
// Create a direct trie based Merkle prover
|
|
||||||
provers = append(provers, func(key []byte) *ethdb.MemDatabase { |
|
||||||
proof := ethdb.NewMemDatabase() |
|
||||||
trie.Prove(key, 0, proof) |
|
||||||
return proof |
|
||||||
}) |
|
||||||
// Create a leaf iterator based Merkle prover
|
|
||||||
provers = append(provers, func(key []byte) *ethdb.MemDatabase { |
|
||||||
proof := ethdb.NewMemDatabase() |
|
||||||
if it := NewIterator(trie.NodeIterator(key)); it.Next() && bytes.Equal(key, it.Key) { |
|
||||||
for _, p := range it.Prove() { |
|
||||||
proof.Put(crypto.Keccak256(p), p) |
|
||||||
} |
|
||||||
} |
|
||||||
return proof |
|
||||||
}) |
|
||||||
return provers |
|
||||||
} |
|
||||||
|
|
||||||
func TestProof(t *testing.T) { |
|
||||||
trie, vals := randomTrie(500) |
|
||||||
root := trie.Hash() |
|
||||||
for i, prover := range makeProvers(trie) { |
|
||||||
for _, kv := range vals { |
|
||||||
proof := prover(kv.k) |
|
||||||
if proof == nil { |
|
||||||
t.Fatalf("prover %d: missing key %x while constructing proof", i, kv.k) |
|
||||||
} |
|
||||||
val, _, err := VerifyProof(root, kv.k, proof) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("prover %d: failed to verify proof for key %x: %v\nraw proof: %x", i, kv.k, err, proof) |
|
||||||
} |
|
||||||
if !bytes.Equal(val, kv.v) { |
|
||||||
t.Fatalf("prover %d: verified value mismatch for key %x: have %x, want %x", i, kv.k, val, kv.v) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestOneElementProof(t *testing.T) { |
|
||||||
trie := new(Trie) |
|
||||||
updateString(trie, "k", "v") |
|
||||||
for i, prover := range makeProvers(trie) { |
|
||||||
proof := prover([]byte("k")) |
|
||||||
if proof == nil { |
|
||||||
t.Fatalf("prover %d: nil proof", i) |
|
||||||
} |
|
||||||
if proof.Len() != 1 { |
|
||||||
t.Errorf("prover %d: proof should have one element", i) |
|
||||||
} |
|
||||||
val, _, err := VerifyProof(trie.Hash(), []byte("k"), proof) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("prover %d: failed to verify proof: %v\nraw proof: %x", i, err, proof) |
|
||||||
} |
|
||||||
if !bytes.Equal(val, []byte("v")) { |
|
||||||
t.Fatalf("prover %d: verified value mismatch: have %x, want 'k'", i, val) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestBadProof(t *testing.T) { |
|
||||||
trie, vals := randomTrie(800) |
|
||||||
root := trie.Hash() |
|
||||||
for i, prover := range makeProvers(trie) { |
|
||||||
for _, kv := range vals { |
|
||||||
proof := prover(kv.k) |
|
||||||
if proof == nil { |
|
||||||
t.Fatalf("prover %d: nil proof", i) |
|
||||||
} |
|
||||||
key := proof.Keys()[mrand.Intn(proof.Len())] |
|
||||||
val, _ := proof.Get(key) |
|
||||||
proof.Delete(key) |
|
||||||
|
|
||||||
mutateByte(val) |
|
||||||
proof.Put(crypto.Keccak256(val), val) |
|
||||||
|
|
||||||
if _, _, err := VerifyProof(root, kv.k, proof); err == nil { |
|
||||||
t.Fatalf("prover %d: expected proof to fail for key %x", i, kv.k) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Tests that missing keys can also be proven. The test explicitly uses a single
|
|
||||||
// entry trie and checks for missing keys both before and after the single entry.
|
|
||||||
func TestMissingKeyProof(t *testing.T) { |
|
||||||
trie := new(Trie) |
|
||||||
updateString(trie, "k", "v") |
|
||||||
|
|
||||||
for i, key := range []string{"a", "j", "l", "z"} { |
|
||||||
proof := ethdb.NewMemDatabase() |
|
||||||
trie.Prove([]byte(key), 0, proof) |
|
||||||
|
|
||||||
if proof.Len() != 1 { |
|
||||||
t.Errorf("test %d: proof should have one element", i) |
|
||||||
} |
|
||||||
val, _, err := VerifyProof(trie.Hash(), []byte(key), proof) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("test %d: failed to verify proof: %v\nraw proof: %x", i, err, proof) |
|
||||||
} |
|
||||||
if val != nil { |
|
||||||
t.Fatalf("test %d: verified value mismatch: have %x, want nil", i, val) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// mutateByte changes one byte in b.
|
|
||||||
func mutateByte(b []byte) { |
|
||||||
for r := mrand.Intn(len(b)); ; { |
|
||||||
new := byte(mrand.Intn(255)) |
|
||||||
if new != b[r] { |
|
||||||
b[r] = new |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func BenchmarkProve(b *testing.B) { |
|
||||||
trie, vals := randomTrie(100) |
|
||||||
var keys []string |
|
||||||
for k := range vals { |
|
||||||
keys = append(keys, k) |
|
||||||
} |
|
||||||
|
|
||||||
b.ResetTimer() |
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
kv := vals[keys[i%len(keys)]] |
|
||||||
proofs := ethdb.NewMemDatabase() |
|
||||||
if trie.Prove(kv.k, 0, proofs); len(proofs.Keys()) == 0 { |
|
||||||
b.Fatalf("zero length proof for %x", kv.k) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func BenchmarkVerifyProof(b *testing.B) { |
|
||||||
trie, vals := randomTrie(100) |
|
||||||
root := trie.Hash() |
|
||||||
var keys []string |
|
||||||
var proofs []*ethdb.MemDatabase |
|
||||||
for k := range vals { |
|
||||||
keys = append(keys, k) |
|
||||||
proof := ethdb.NewMemDatabase() |
|
||||||
trie.Prove([]byte(k), 0, proof) |
|
||||||
proofs = append(proofs, proof) |
|
||||||
} |
|
||||||
|
|
||||||
b.ResetTimer() |
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
im := i % len(keys) |
|
||||||
if _, _, err := VerifyProof(root, []byte(keys[im]), proofs[im]); err != nil { |
|
||||||
b.Fatalf("key %x: %v", keys[im], err) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func randomTrie(n int) (*Trie, map[string]*kv) { |
|
||||||
trie := new(Trie) |
|
||||||
vals := make(map[string]*kv) |
|
||||||
for i := byte(0); i < 100; i++ { |
|
||||||
value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} |
|
||||||
value2 := &kv{common.LeftPadBytes([]byte{i + 10}, 32), []byte{i}, false} |
|
||||||
trie.Update(value.k, value.v) |
|
||||||
trie.Update(value2.k, value2.v) |
|
||||||
vals[string(value.k)] = value |
|
||||||
vals[string(value2.k)] = value2 |
|
||||||
} |
|
||||||
for i := 0; i < n; i++ { |
|
||||||
value := &kv{randBytes(32), randBytes(20), false} |
|
||||||
trie.Update(value.k, value.v) |
|
||||||
vals[string(value.k)] = value |
|
||||||
} |
|
||||||
return trie, vals |
|
||||||
} |
|
||||||
|
|
||||||
func randBytes(n int) []byte { |
|
||||||
r := make([]byte, n) |
|
||||||
crand.Read(r) |
|
||||||
return r |
|
||||||
} |
|
@ -1,203 +0,0 @@ |
|||||||
// Copyright 2015 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/log" |
|
||||||
) |
|
||||||
|
|
||||||
// SecureTrie wraps a trie with key hashing. In a secure trie, all
|
|
||||||
// access operations hash the key using keccak256. This prevents
|
|
||||||
// calling code from creating long chains of nodes that
|
|
||||||
// increase the access time.
|
|
||||||
//
|
|
||||||
// Contrary to a regular trie, a SecureTrie can only be created with
|
|
||||||
// New and must have an attached database. The database also stores
|
|
||||||
// the preimage of each key.
|
|
||||||
//
|
|
||||||
// SecureTrie is not safe for concurrent use.
|
|
||||||
type SecureTrie struct { |
|
||||||
trie Trie |
|
||||||
hashKeyBuf [common.HashLength]byte |
|
||||||
secKeyCache map[string][]byte |
|
||||||
secKeyCacheOwner *SecureTrie // Pointer to self, replace the key cache on mismatch
|
|
||||||
} |
|
||||||
|
|
||||||
// NewSecure creates a trie with an existing root node from a backing database
|
|
||||||
// and optional intermediate in-memory node pool.
|
|
||||||
//
|
|
||||||
// If root is the zero hash or the sha3 hash of an empty string, the
|
|
||||||
// trie is initially empty. Otherwise, New will panic if db is nil
|
|
||||||
// and returns MissingNodeError if the root node cannot be found.
|
|
||||||
//
|
|
||||||
// Accessing the trie loads nodes from the database or node pool on demand.
|
|
||||||
// Loaded nodes are kept around until their 'cache generation' expires.
|
|
||||||
// A new cache generation is created by each call to Commit.
|
|
||||||
// cachelimit sets the number of past cache generations to keep.
|
|
||||||
func NewSecure(root common.Hash, db *Database, cachelimit uint16) (*SecureTrie, error) { |
|
||||||
if db == nil { |
|
||||||
panic("trie.NewSecure called without a database") |
|
||||||
} |
|
||||||
trie, err := New(root, db) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
trie.SetCacheLimit(cachelimit) |
|
||||||
return &SecureTrie{trie: *trie}, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Get returns the value for key stored in the trie.
|
|
||||||
// The value bytes must not be modified by the caller.
|
|
||||||
func (t *SecureTrie) Get(key []byte) []byte { |
|
||||||
res, err := t.TryGet(key) |
|
||||||
if err != nil { |
|
||||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) |
|
||||||
} |
|
||||||
return res |
|
||||||
} |
|
||||||
|
|
||||||
// TryGet returns the value for key stored in the trie.
|
|
||||||
// The value bytes must not be modified by the caller.
|
|
||||||
// If a node was not found in the database, a MissingNodeError is returned.
|
|
||||||
func (t *SecureTrie) TryGet(key []byte) ([]byte, error) { |
|
||||||
return t.trie.TryGet(t.hashKey(key)) |
|
||||||
} |
|
||||||
|
|
||||||
// Update associates key with value in the trie. Subsequent calls to
|
|
||||||
// Get will return value. If value has length zero, any existing value
|
|
||||||
// is deleted from the trie and calls to Get will return nil.
|
|
||||||
//
|
|
||||||
// The value bytes must not be modified by the caller while they are
|
|
||||||
// stored in the trie.
|
|
||||||
func (t *SecureTrie) Update(key, value []byte) { |
|
||||||
if err := t.TryUpdate(key, value); err != nil { |
|
||||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// TryUpdate associates key with value in the trie. Subsequent calls to
|
|
||||||
// Get will return value. If value has length zero, any existing value
|
|
||||||
// is deleted from the trie and calls to Get will return nil.
|
|
||||||
//
|
|
||||||
// The value bytes must not be modified by the caller while they are
|
|
||||||
// stored in the trie.
|
|
||||||
//
|
|
||||||
// If a node was not found in the database, a MissingNodeError is returned.
|
|
||||||
func (t *SecureTrie) TryUpdate(key, value []byte) error { |
|
||||||
hk := t.hashKey(key) |
|
||||||
err := t.trie.TryUpdate(hk, value) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
t.getSecKeyCache()[string(hk)] = common.CopyBytes(key) |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Delete removes any existing value for key from the trie.
|
|
||||||
func (t *SecureTrie) Delete(key []byte) { |
|
||||||
if err := t.TryDelete(key); err != nil { |
|
||||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// TryDelete removes any existing value for key from the trie.
|
|
||||||
// If a node was not found in the database, a MissingNodeError is returned.
|
|
||||||
func (t *SecureTrie) TryDelete(key []byte) error { |
|
||||||
hk := t.hashKey(key) |
|
||||||
delete(t.getSecKeyCache(), string(hk)) |
|
||||||
return t.trie.TryDelete(hk) |
|
||||||
} |
|
||||||
|
|
||||||
// GetKey returns the sha3 preimage of a hashed key that was
|
|
||||||
// previously used to store a value.
|
|
||||||
func (t *SecureTrie) GetKey(shaKey []byte) []byte { |
|
||||||
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok { |
|
||||||
return key |
|
||||||
} |
|
||||||
key, _ := t.trie.db.preimage(common.BytesToHash(shaKey)) |
|
||||||
return key |
|
||||||
} |
|
||||||
|
|
||||||
// Commit writes all nodes and the secure hash pre-images to the trie's database.
|
|
||||||
// Nodes are stored with their sha3 hash as the key.
|
|
||||||
//
|
|
||||||
// Committing flushes nodes from memory. Subsequent Get calls will load nodes
|
|
||||||
// from the database.
|
|
||||||
func (t *SecureTrie) Commit(onleaf LeafCallback) (root common.Hash, err error) { |
|
||||||
// Write all the pre-images to the actual disk database
|
|
||||||
if len(t.getSecKeyCache()) > 0 { |
|
||||||
t.trie.db.lock.Lock() |
|
||||||
for hk, key := range t.secKeyCache { |
|
||||||
t.trie.db.insertPreimage(common.BytesToHash([]byte(hk)), key) |
|
||||||
} |
|
||||||
t.trie.db.lock.Unlock() |
|
||||||
|
|
||||||
t.secKeyCache = make(map[string][]byte) |
|
||||||
} |
|
||||||
// Commit the trie to its intermediate node database
|
|
||||||
return t.trie.Commit(onleaf) |
|
||||||
} |
|
||||||
|
|
||||||
// Hash returns the root hash of SecureTrie. It does not write to the
|
|
||||||
// database and can be used even if the trie doesn't have one.
|
|
||||||
func (t *SecureTrie) Hash() common.Hash { |
|
||||||
return t.trie.Hash() |
|
||||||
} |
|
||||||
|
|
||||||
// Root returns the root hash of SecureTrie.
|
|
||||||
// Deprecated: use Hash instead.
|
|
||||||
func (t *SecureTrie) Root() []byte { |
|
||||||
return t.trie.Root() |
|
||||||
} |
|
||||||
|
|
||||||
// Copy returns a copy of SecureTrie.
|
|
||||||
func (t *SecureTrie) Copy() *SecureTrie { |
|
||||||
cpy := *t |
|
||||||
return &cpy |
|
||||||
} |
|
||||||
|
|
||||||
// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
|
|
||||||
// starts at the key after the given start key.
|
|
||||||
func (t *SecureTrie) NodeIterator(start []byte) NodeIterator { |
|
||||||
return t.trie.NodeIterator(start) |
|
||||||
} |
|
||||||
|
|
||||||
// hashKey returns the hash of key as an ephemeral buffer.
|
|
||||||
// The caller must not hold onto the return value because it will become
|
|
||||||
// invalid on the next call to hashKey or secKey.
|
|
||||||
func (t *SecureTrie) hashKey(key []byte) []byte { |
|
||||||
h := newHasher(0, 0, nil) |
|
||||||
h.sha.Reset() |
|
||||||
h.sha.Write(key) |
|
||||||
buf := h.sha.Sum(t.hashKeyBuf[:0]) |
|
||||||
returnHasherToPool(h) |
|
||||||
return buf |
|
||||||
} |
|
||||||
|
|
||||||
// getSecKeyCache returns the current secure key cache, creating a new one if
|
|
||||||
// ownership changed (i.e. the current secure trie is a copy of another owning
|
|
||||||
// the actual cache).
|
|
||||||
func (t *SecureTrie) getSecKeyCache() map[string][]byte { |
|
||||||
if t != t.secKeyCacheOwner { |
|
||||||
t.secKeyCacheOwner = t |
|
||||||
t.secKeyCache = make(map[string][]byte) |
|
||||||
} |
|
||||||
return t.secKeyCache |
|
||||||
} |
|
@ -1,145 +0,0 @@ |
|||||||
// Copyright 2015 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"runtime" |
|
||||||
"sync" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/crypto" |
|
||||||
"github.com/harmony-one/harmony/internal/db" |
|
||||||
) |
|
||||||
|
|
||||||
func newEmptySecure() *SecureTrie { |
|
||||||
trie, _ := NewSecure(common.Hash{}, NewDatabase(db.NewMemDatabase()), 0) |
|
||||||
return trie |
|
||||||
} |
|
||||||
|
|
||||||
// makeTestSecureTrie creates a large enough secure trie for testing.
|
|
||||||
func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) { |
|
||||||
// Create an empty trie
|
|
||||||
triedb := NewDatabase(db.NewMemDatabase()) |
|
||||||
|
|
||||||
trie, _ := NewSecure(common.Hash{}, triedb, 0) |
|
||||||
|
|
||||||
// Fill it with some arbitrary data
|
|
||||||
content := make(map[string][]byte) |
|
||||||
for i := byte(0); i < 255; i++ { |
|
||||||
// Map the same data under multiple keys
|
|
||||||
key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i} |
|
||||||
content[string(key)] = val |
|
||||||
trie.Update(key, val) |
|
||||||
|
|
||||||
key, val = common.LeftPadBytes([]byte{2, i}, 32), []byte{i} |
|
||||||
content[string(key)] = val |
|
||||||
trie.Update(key, val) |
|
||||||
|
|
||||||
// Add some other data to inflate the trie
|
|
||||||
for j := byte(3); j < 13; j++ { |
|
||||||
key, val = common.LeftPadBytes([]byte{j, i}, 32), []byte{j, i} |
|
||||||
content[string(key)] = val |
|
||||||
trie.Update(key, val) |
|
||||||
} |
|
||||||
} |
|
||||||
trie.Commit(nil) |
|
||||||
|
|
||||||
// Return the generated trie
|
|
||||||
return triedb, trie, content |
|
||||||
} |
|
||||||
|
|
||||||
func TestSecureDelete(t *testing.T) { |
|
||||||
trie := newEmptySecure() |
|
||||||
vals := []struct{ k, v string }{ |
|
||||||
{"do", "verb"}, |
|
||||||
{"ether", "wookiedoo"}, |
|
||||||
{"horse", "stallion"}, |
|
||||||
{"shaman", "horse"}, |
|
||||||
{"doge", "coin"}, |
|
||||||
{"ether", ""}, |
|
||||||
{"dog", "puppy"}, |
|
||||||
{"shaman", ""}, |
|
||||||
} |
|
||||||
for _, val := range vals { |
|
||||||
if val.v != "" { |
|
||||||
trie.Update([]byte(val.k), []byte(val.v)) |
|
||||||
} else { |
|
||||||
trie.Delete([]byte(val.k)) |
|
||||||
} |
|
||||||
} |
|
||||||
hash := trie.Hash() |
|
||||||
exp := common.HexToHash("29b235a58c3c25ab83010c327d5932bcf05324b7d6b1185e650798034783ca9d") |
|
||||||
if hash != exp { |
|
||||||
t.Errorf("expected %x got %x", exp, hash) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestSecureGetKey(t *testing.T) { |
|
||||||
trie := newEmptySecure() |
|
||||||
trie.Update([]byte("foo"), []byte("bar")) |
|
||||||
|
|
||||||
key := []byte("foo") |
|
||||||
value := []byte("bar") |
|
||||||
seckey := crypto.Keccak256(key) |
|
||||||
|
|
||||||
if !bytes.Equal(trie.Get(key), value) { |
|
||||||
t.Errorf("Get did not return bar") |
|
||||||
} |
|
||||||
if k := trie.GetKey(seckey); !bytes.Equal(k, key) { |
|
||||||
t.Errorf("GetKey returned %q, want %q", k, key) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestSecureTrieConcurrency(t *testing.T) { |
|
||||||
// Create an initial trie and copy if for concurrent access
|
|
||||||
_, trie, _ := makeTestSecureTrie() |
|
||||||
|
|
||||||
threads := runtime.NumCPU() |
|
||||||
tries := make([]*SecureTrie, threads) |
|
||||||
for i := 0; i < threads; i++ { |
|
||||||
cpy := *trie |
|
||||||
tries[i] = &cpy |
|
||||||
} |
|
||||||
// Start a batch of goroutines interactng with the trie
|
|
||||||
pend := new(sync.WaitGroup) |
|
||||||
pend.Add(threads) |
|
||||||
for i := 0; i < threads; i++ { |
|
||||||
go func(index int) { |
|
||||||
defer pend.Done() |
|
||||||
|
|
||||||
for j := byte(0); j < 255; j++ { |
|
||||||
// Map the same data under multiple keys
|
|
||||||
key, val := common.LeftPadBytes([]byte{byte(index), 1, j}, 32), []byte{j} |
|
||||||
tries[index].Update(key, val) |
|
||||||
|
|
||||||
key, val = common.LeftPadBytes([]byte{byte(index), 2, j}, 32), []byte{j} |
|
||||||
tries[index].Update(key, val) |
|
||||||
|
|
||||||
// Add some other data to inflate the trie
|
|
||||||
for k := byte(3); k < 13; k++ { |
|
||||||
key, val = common.LeftPadBytes([]byte{byte(index), k, j}, 32), []byte{k, j} |
|
||||||
tries[index].Update(key, val) |
|
||||||
} |
|
||||||
} |
|
||||||
tries[index].Commit(nil) |
|
||||||
}(i) |
|
||||||
} |
|
||||||
// Wait for all threads to finish
|
|
||||||
pend.Wait() |
|
||||||
} |
|
@ -1,330 +0,0 @@ |
|||||||
// Copyright 2015 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/common/prque" |
|
||||||
"github.com/ethereum/go-ethereum/ethdb" |
|
||||||
) |
|
||||||
|
|
||||||
// ErrNotRequested is returned by the trie sync when it's requested to process a
|
|
||||||
// node it did not request.
|
|
||||||
var ErrNotRequested = errors.New("not requested") |
|
||||||
|
|
||||||
// ErrAlreadyProcessed is returned by the trie sync when it's requested to process a
|
|
||||||
// node it already processed previously.
|
|
||||||
var ErrAlreadyProcessed = errors.New("already processed") |
|
||||||
|
|
||||||
// request represents a scheduled or already in-flight state retrieval request.
|
|
||||||
type request struct { |
|
||||||
hash common.Hash // Hash of the node data content to retrieve
|
|
||||||
data []byte // Data content of the node, cached until all subtrees complete
|
|
||||||
raw bool // Whether this is a raw entry (code) or a trie node
|
|
||||||
|
|
||||||
parents []*request // Parent state nodes referencing this entry (notify all upon completion)
|
|
||||||
depth int // Depth level within the trie the node is located to prioritise DFS
|
|
||||||
deps int // Number of dependencies before allowed to commit this node
|
|
||||||
|
|
||||||
callback LeafCallback // Callback to invoke if a leaf node it reached on this branch
|
|
||||||
} |
|
||||||
|
|
||||||
// SyncResult is a simple list to return missing nodes along with their request
|
|
||||||
// hashes.
|
|
||||||
type SyncResult struct { |
|
||||||
Hash common.Hash // Hash of the originally unknown trie node
|
|
||||||
Data []byte // Data content of the retrieved node
|
|
||||||
} |
|
||||||
|
|
||||||
// syncMemBatch is an in-memory buffer of successfully downloaded but not yet
|
|
||||||
// persisted data items.
|
|
||||||
type syncMemBatch struct { |
|
||||||
batch map[common.Hash][]byte // In-memory membatch of recently completed items
|
|
||||||
order []common.Hash // Order of completion to prevent out-of-order data loss
|
|
||||||
} |
|
||||||
|
|
||||||
// newSyncMemBatch allocates a new memory-buffer for not-yet persisted trie nodes.
|
|
||||||
func newSyncMemBatch() *syncMemBatch { |
|
||||||
return &syncMemBatch{ |
|
||||||
batch: make(map[common.Hash][]byte), |
|
||||||
order: make([]common.Hash, 0, 256), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Sync is the main state trie synchronisation scheduler, which provides yet
|
|
||||||
// unknown trie hashes to retrieve, accepts node data associated with said hashes
|
|
||||||
// and reconstructs the trie step by step until all is done.
|
|
||||||
type Sync struct { |
|
||||||
database DatabaseReader // Persistent database to check for existing entries
|
|
||||||
membatch *syncMemBatch // Memory buffer to avoid frequent database writes
|
|
||||||
requests map[common.Hash]*request // Pending requests pertaining to a key hash
|
|
||||||
queue *prque.Prque // Priority queue with the pending requests
|
|
||||||
} |
|
||||||
|
|
||||||
// NewSync creates a new trie data download scheduler.
|
|
||||||
func NewSync(root common.Hash, database DatabaseReader, callback LeafCallback) *Sync { |
|
||||||
ts := &Sync{ |
|
||||||
database: database, |
|
||||||
membatch: newSyncMemBatch(), |
|
||||||
requests: make(map[common.Hash]*request), |
|
||||||
queue: prque.New(nil), |
|
||||||
} |
|
||||||
ts.AddSubTrie(root, 0, common.Hash{}, callback) |
|
||||||
return ts |
|
||||||
} |
|
||||||
|
|
||||||
// AddSubTrie registers a new trie to the sync code, rooted at the designated parent.
|
|
||||||
func (s *Sync) AddSubTrie(root common.Hash, depth int, parent common.Hash, callback LeafCallback) { |
|
||||||
// Short circuit if the trie is empty or already known
|
|
||||||
if root == emptyRoot { |
|
||||||
return |
|
||||||
} |
|
||||||
if _, ok := s.membatch.batch[root]; ok { |
|
||||||
return |
|
||||||
} |
|
||||||
key := root.Bytes() |
|
||||||
blob, _ := s.database.Get(key) |
|
||||||
if local, err := decodeNode(key, blob, 0); local != nil && err == nil { |
|
||||||
return |
|
||||||
} |
|
||||||
// Assemble the new sub-trie sync request
|
|
||||||
req := &request{ |
|
||||||
hash: root, |
|
||||||
depth: depth, |
|
||||||
callback: callback, |
|
||||||
} |
|
||||||
// If this sub-trie has a designated parent, link them together
|
|
||||||
if parent != (common.Hash{}) { |
|
||||||
ancestor := s.requests[parent] |
|
||||||
if ancestor == nil { |
|
||||||
panic(fmt.Sprintf("sub-trie ancestor not found: %x", parent)) |
|
||||||
} |
|
||||||
ancestor.deps++ |
|
||||||
req.parents = append(req.parents, ancestor) |
|
||||||
} |
|
||||||
s.schedule(req) |
|
||||||
} |
|
||||||
|
|
||||||
// AddRawEntry schedules the direct retrieval of a state entry that should not be
|
|
||||||
// interpreted as a trie node, but rather accepted and stored into the database
|
|
||||||
// as is. This method's goal is to support misc state metadata retrievals (e.g.
|
|
||||||
// contract code).
|
|
||||||
func (s *Sync) AddRawEntry(hash common.Hash, depth int, parent common.Hash) { |
|
||||||
// Short circuit if the entry is empty or already known
|
|
||||||
if hash == emptyState { |
|
||||||
return |
|
||||||
} |
|
||||||
if _, ok := s.membatch.batch[hash]; ok { |
|
||||||
return |
|
||||||
} |
|
||||||
if ok, _ := s.database.Has(hash.Bytes()); ok { |
|
||||||
return |
|
||||||
} |
|
||||||
// Assemble the new sub-trie sync request
|
|
||||||
req := &request{ |
|
||||||
hash: hash, |
|
||||||
raw: true, |
|
||||||
depth: depth, |
|
||||||
} |
|
||||||
// If this sub-trie has a designated parent, link them together
|
|
||||||
if parent != (common.Hash{}) { |
|
||||||
ancestor := s.requests[parent] |
|
||||||
if ancestor == nil { |
|
||||||
panic(fmt.Sprintf("raw-entry ancestor not found: %x", parent)) |
|
||||||
} |
|
||||||
ancestor.deps++ |
|
||||||
req.parents = append(req.parents, ancestor) |
|
||||||
} |
|
||||||
s.schedule(req) |
|
||||||
} |
|
||||||
|
|
||||||
// Missing retrieves the known missing nodes from the trie for retrieval.
|
|
||||||
func (s *Sync) Missing(max int) []common.Hash { |
|
||||||
requests := []common.Hash{} |
|
||||||
for !s.queue.Empty() && (max == 0 || len(requests) < max) { |
|
||||||
requests = append(requests, s.queue.PopItem().(common.Hash)) |
|
||||||
} |
|
||||||
return requests |
|
||||||
} |
|
||||||
|
|
||||||
// Process injects a batch of retrieved trie nodes data, returning if something
|
|
||||||
// was committed to the database and also the index of an entry if processing of
|
|
||||||
// it failed.
|
|
||||||
func (s *Sync) Process(results []SyncResult) (bool, int, error) { |
|
||||||
committed := false |
|
||||||
|
|
||||||
for i, item := range results { |
|
||||||
// If the item was not requested, bail out
|
|
||||||
request := s.requests[item.Hash] |
|
||||||
if request == nil { |
|
||||||
return committed, i, ErrNotRequested |
|
||||||
} |
|
||||||
if request.data != nil { |
|
||||||
return committed, i, ErrAlreadyProcessed |
|
||||||
} |
|
||||||
// If the item is a raw entry request, commit directly
|
|
||||||
if request.raw { |
|
||||||
request.data = item.Data |
|
||||||
s.commit(request) |
|
||||||
committed = true |
|
||||||
continue |
|
||||||
} |
|
||||||
// Decode the node data content and update the request
|
|
||||||
node, err := decodeNode(item.Hash[:], item.Data, 0) |
|
||||||
if err != nil { |
|
||||||
return committed, i, err |
|
||||||
} |
|
||||||
request.data = item.Data |
|
||||||
|
|
||||||
// Create and schedule a request for all the children nodes
|
|
||||||
requests, err := s.children(request, node) |
|
||||||
if err != nil { |
|
||||||
return committed, i, err |
|
||||||
} |
|
||||||
if len(requests) == 0 && request.deps == 0 { |
|
||||||
s.commit(request) |
|
||||||
committed = true |
|
||||||
continue |
|
||||||
} |
|
||||||
request.deps += len(requests) |
|
||||||
for _, child := range requests { |
|
||||||
s.schedule(child) |
|
||||||
} |
|
||||||
} |
|
||||||
return committed, 0, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Commit flushes the data stored in the internal membatch out to persistent
|
|
||||||
// storage, returning the number of items written and any occurred error.
|
|
||||||
func (s *Sync) Commit(dbw ethdb.Putter) (int, error) { |
|
||||||
// Dump the membatch into a database dbw
|
|
||||||
for i, key := range s.membatch.order { |
|
||||||
if err := dbw.Put(key[:], s.membatch.batch[key]); err != nil { |
|
||||||
return i, err |
|
||||||
} |
|
||||||
} |
|
||||||
written := len(s.membatch.order) |
|
||||||
|
|
||||||
// Drop the membatch data and return
|
|
||||||
s.membatch = newSyncMemBatch() |
|
||||||
return written, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Pending returns the number of state entries currently pending for download.
|
|
||||||
func (s *Sync) Pending() int { |
|
||||||
return len(s.requests) |
|
||||||
} |
|
||||||
|
|
||||||
// schedule inserts a new state retrieval request into the fetch queue. If there
|
|
||||||
// is already a pending request for this node, the new request will be discarded
|
|
||||||
// and only a parent reference added to the old one.
|
|
||||||
func (s *Sync) schedule(req *request) { |
|
||||||
// If we're already requesting this node, add a new reference and stop
|
|
||||||
if old, ok := s.requests[req.hash]; ok { |
|
||||||
old.parents = append(old.parents, req.parents...) |
|
||||||
return |
|
||||||
} |
|
||||||
// Schedule the request for future retrieval
|
|
||||||
s.queue.Push(req.hash, int64(req.depth)) |
|
||||||
s.requests[req.hash] = req |
|
||||||
} |
|
||||||
|
|
||||||
// children retrieves all the missing children of a state trie entry for future
|
|
||||||
// retrieval scheduling.
|
|
||||||
func (s *Sync) children(req *request, object node) ([]*request, error) { |
|
||||||
// Gather all the children of the node, irrelevant whether known or not
|
|
||||||
type child struct { |
|
||||||
node node |
|
||||||
depth int |
|
||||||
} |
|
||||||
children := []child{} |
|
||||||
|
|
||||||
switch node := (object).(type) { |
|
||||||
case *shortNode: |
|
||||||
children = []child{{ |
|
||||||
node: node.Val, |
|
||||||
depth: req.depth + len(node.Key), |
|
||||||
}} |
|
||||||
case *fullNode: |
|
||||||
for i := 0; i < 17; i++ { |
|
||||||
if node.Children[i] != nil { |
|
||||||
children = append(children, child{ |
|
||||||
node: node.Children[i], |
|
||||||
depth: req.depth + 1, |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
default: |
|
||||||
panic(fmt.Sprintf("unknown node: %+v", node)) |
|
||||||
} |
|
||||||
// Iterate over the children, and request all unknown ones
|
|
||||||
requests := make([]*request, 0, len(children)) |
|
||||||
for _, child := range children { |
|
||||||
// Notify any external watcher of a new key/value node
|
|
||||||
if req.callback != nil { |
|
||||||
if node, ok := (child.node).(valueNode); ok { |
|
||||||
if err := req.callback(node, req.hash); err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
// If the child references another node, resolve or schedule
|
|
||||||
if node, ok := (child.node).(hashNode); ok { |
|
||||||
// Try to resolve the node from the local database
|
|
||||||
hash := common.BytesToHash(node) |
|
||||||
if _, ok := s.membatch.batch[hash]; ok { |
|
||||||
continue |
|
||||||
} |
|
||||||
if ok, _ := s.database.Has(node); ok { |
|
||||||
continue |
|
||||||
} |
|
||||||
// Locally unknown node, schedule for retrieval
|
|
||||||
requests = append(requests, &request{ |
|
||||||
hash: hash, |
|
||||||
parents: []*request{req}, |
|
||||||
depth: child.depth, |
|
||||||
callback: req.callback, |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
||||||
return requests, nil |
|
||||||
} |
|
||||||
|
|
||||||
// commit finalizes a retrieval request and stores it into the membatch. If any
|
|
||||||
// of the referencing parent requests complete due to this commit, they are also
|
|
||||||
// committed themselves.
|
|
||||||
func (s *Sync) commit(req *request) (err error) { |
|
||||||
// Write the node content to the membatch
|
|
||||||
s.membatch.batch[req.hash] = req.data |
|
||||||
s.membatch.order = append(s.membatch.order, req.hash) |
|
||||||
|
|
||||||
delete(s.requests, req.hash) |
|
||||||
|
|
||||||
// Check all parents for completion
|
|
||||||
for _, parent := range req.parents { |
|
||||||
parent.deps-- |
|
||||||
if parent.deps == 0 { |
|
||||||
if err := s.commit(parent); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
@ -1,358 +0,0 @@ |
|||||||
// Copyright 2015 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/harmony-one/harmony/internal/db" |
|
||||||
) |
|
||||||
|
|
||||||
// makeTestTrie create a sample test trie to test node-wise reconstruction.
|
|
||||||
func makeTestTrie() (*Database, *Trie, map[string][]byte) { |
|
||||||
// Create an empty trie
|
|
||||||
triedb := NewDatabase(db.NewMemDatabase()) |
|
||||||
trie, _ := New(common.Hash{}, triedb) |
|
||||||
|
|
||||||
// Fill it with some arbitrary data
|
|
||||||
content := make(map[string][]byte) |
|
||||||
for i := byte(0); i < 255; i++ { |
|
||||||
// Map the same data under multiple keys
|
|
||||||
key, val := common.LeftPadBytes([]byte{1, i}, 32), []byte{i} |
|
||||||
content[string(key)] = val |
|
||||||
trie.Update(key, val) |
|
||||||
|
|
||||||
key, val = common.LeftPadBytes([]byte{2, i}, 32), []byte{i} |
|
||||||
content[string(key)] = val |
|
||||||
trie.Update(key, val) |
|
||||||
|
|
||||||
// Add some other data to inflate the trie
|
|
||||||
for j := byte(3); j < 13; j++ { |
|
||||||
key, val = common.LeftPadBytes([]byte{j, i}, 32), []byte{j, i} |
|
||||||
content[string(key)] = val |
|
||||||
trie.Update(key, val) |
|
||||||
} |
|
||||||
} |
|
||||||
trie.Commit(nil) |
|
||||||
|
|
||||||
// Return the generated trie
|
|
||||||
return triedb, trie, content |
|
||||||
} |
|
||||||
|
|
||||||
// checkTrieContents cross references a reconstructed trie with an expected data
|
|
||||||
// content map.
|
|
||||||
func checkTrieContents(t *testing.T, db *Database, root []byte, content map[string][]byte) { |
|
||||||
// Check root availability and trie contents
|
|
||||||
trie, err := New(common.BytesToHash(root), db) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("failed to create trie at %x: %v", root, err) |
|
||||||
} |
|
||||||
if err := checkTrieConsistency(db, common.BytesToHash(root)); err != nil { |
|
||||||
t.Fatalf("inconsistent trie at %x: %v", root, err) |
|
||||||
} |
|
||||||
for key, val := range content { |
|
||||||
if have := trie.Get([]byte(key)); !bytes.Equal(have, val) { |
|
||||||
t.Errorf("entry %x: content mismatch: have %x, want %x", key, have, val) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// checkTrieConsistency checks that all nodes in a trie are indeed present.
|
|
||||||
func checkTrieConsistency(db *Database, root common.Hash) error { |
|
||||||
// Create and iterate a trie rooted in a subnode
|
|
||||||
trie, err := New(root, db) |
|
||||||
if err != nil { |
|
||||||
return nil // Consider a non existent state consistent
|
|
||||||
} |
|
||||||
it := trie.NodeIterator(nil) |
|
||||||
for it.Next(true) { |
|
||||||
} |
|
||||||
return it.Error() |
|
||||||
} |
|
||||||
|
|
||||||
// Tests that an empty trie is not scheduled for syncing.
|
|
||||||
func TestEmptySync(t *testing.T) { |
|
||||||
dbA := NewDatabase(db.NewMemDatabase()) |
|
||||||
dbB := NewDatabase(db.NewMemDatabase()) |
|
||||||
emptyA, _ := New(common.Hash{}, dbA) |
|
||||||
emptyB, _ := New(emptyRoot, dbB) |
|
||||||
|
|
||||||
for i, trie := range []*Trie{emptyA, emptyB} { |
|
||||||
if req := NewSync(trie.Hash(), db.NewMemDatabase(), nil).Missing(1); len(req) != 0 { |
|
||||||
t.Errorf("test %d: content requested for empty trie: %v", i, req) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Tests that given a root hash, a trie can sync iteratively on a single thread,
|
|
||||||
// requesting retrieval tasks and returning all of them in one go.
|
|
||||||
func TestIterativeSyncIndividual(t *testing.T) { testIterativeSync(t, 1) } |
|
||||||
func TestIterativeSyncBatched(t *testing.T) { testIterativeSync(t, 100) } |
|
||||||
|
|
||||||
func testIterativeSync(t *testing.T, batch int) { |
|
||||||
// Create a random trie to copy
|
|
||||||
srcDb, srcTrie, srcData := makeTestTrie() |
|
||||||
|
|
||||||
// Create a destination trie and sync with the scheduler
|
|
||||||
diskdb := db.NewMemDatabase() |
|
||||||
triedb := NewDatabase(diskdb) |
|
||||||
sched := NewSync(srcTrie.Hash(), diskdb, nil) |
|
||||||
|
|
||||||
queue := append([]common.Hash{}, sched.Missing(batch)...) |
|
||||||
for len(queue) > 0 { |
|
||||||
results := make([]SyncResult, len(queue)) |
|
||||||
for i, hash := range queue { |
|
||||||
data, err := srcDb.Node(hash) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) |
|
||||||
} |
|
||||||
results[i] = SyncResult{hash, data} |
|
||||||
} |
|
||||||
if _, index, err := sched.Process(results); err != nil { |
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err) |
|
||||||
} |
|
||||||
if index, err := sched.Commit(diskdb); err != nil { |
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err) |
|
||||||
} |
|
||||||
queue = append(queue[:0], sched.Missing(batch)...) |
|
||||||
} |
|
||||||
// Cross check that the two tries are in sync
|
|
||||||
checkTrieContents(t, triedb, srcTrie.Root(), srcData) |
|
||||||
} |
|
||||||
|
|
||||||
// Tests that the trie scheduler can correctly reconstruct the state even if only
|
|
||||||
// partial results are returned, and the others sent only later.
|
|
||||||
func TestIterativeDelayedSync(t *testing.T) { |
|
||||||
// Create a random trie to copy
|
|
||||||
srcDb, srcTrie, srcData := makeTestTrie() |
|
||||||
|
|
||||||
// Create a destination trie and sync with the scheduler
|
|
||||||
diskdb := db.NewMemDatabase() |
|
||||||
triedb := NewDatabase(diskdb) |
|
||||||
sched := NewSync(srcTrie.Hash(), diskdb, nil) |
|
||||||
|
|
||||||
queue := append([]common.Hash{}, sched.Missing(10000)...) |
|
||||||
for len(queue) > 0 { |
|
||||||
// Sync only half of the scheduled nodes
|
|
||||||
results := make([]SyncResult, len(queue)/2+1) |
|
||||||
for i, hash := range queue[:len(results)] { |
|
||||||
data, err := srcDb.Node(hash) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) |
|
||||||
} |
|
||||||
results[i] = SyncResult{hash, data} |
|
||||||
} |
|
||||||
if _, index, err := sched.Process(results); err != nil { |
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err) |
|
||||||
} |
|
||||||
if index, err := sched.Commit(diskdb); err != nil { |
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err) |
|
||||||
} |
|
||||||
queue = append(queue[len(results):], sched.Missing(10000)...) |
|
||||||
} |
|
||||||
// Cross check that the two tries are in sync
|
|
||||||
checkTrieContents(t, triedb, srcTrie.Root(), srcData) |
|
||||||
} |
|
||||||
|
|
||||||
// Tests that given a root hash, a trie can sync iteratively on a single thread,
|
|
||||||
// requesting retrieval tasks and returning all of them in one go, however in a
|
|
||||||
// random order.
|
|
||||||
func TestIterativeRandomSyncIndividual(t *testing.T) { testIterativeRandomSync(t, 1) } |
|
||||||
func TestIterativeRandomSyncBatched(t *testing.T) { testIterativeRandomSync(t, 100) } |
|
||||||
|
|
||||||
func testIterativeRandomSync(t *testing.T, batch int) { |
|
||||||
// Create a random trie to copy
|
|
||||||
srcDb, srcTrie, srcData := makeTestTrie() |
|
||||||
|
|
||||||
// Create a destination trie and sync with the scheduler
|
|
||||||
diskdb := db.NewMemDatabase() |
|
||||||
triedb := NewDatabase(diskdb) |
|
||||||
sched := NewSync(srcTrie.Hash(), diskdb, nil) |
|
||||||
|
|
||||||
queue := make(map[common.Hash]struct{}) |
|
||||||
for _, hash := range sched.Missing(batch) { |
|
||||||
queue[hash] = struct{}{} |
|
||||||
} |
|
||||||
for len(queue) > 0 { |
|
||||||
// Fetch all the queued nodes in a random order
|
|
||||||
results := make([]SyncResult, 0, len(queue)) |
|
||||||
for hash := range queue { |
|
||||||
data, err := srcDb.Node(hash) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) |
|
||||||
} |
|
||||||
results = append(results, SyncResult{hash, data}) |
|
||||||
} |
|
||||||
// Feed the retrieved results back and queue new tasks
|
|
||||||
if _, index, err := sched.Process(results); err != nil { |
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err) |
|
||||||
} |
|
||||||
if index, err := sched.Commit(diskdb); err != nil { |
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err) |
|
||||||
} |
|
||||||
queue = make(map[common.Hash]struct{}) |
|
||||||
for _, hash := range sched.Missing(batch) { |
|
||||||
queue[hash] = struct{}{} |
|
||||||
} |
|
||||||
} |
|
||||||
// Cross check that the two tries are in sync
|
|
||||||
checkTrieContents(t, triedb, srcTrie.Root(), srcData) |
|
||||||
} |
|
||||||
|
|
||||||
// Tests that the trie scheduler can correctly reconstruct the state even if only
|
|
||||||
// partial results are returned (Even those randomly), others sent only later.
|
|
||||||
func TestIterativeRandomDelayedSync(t *testing.T) { |
|
||||||
// Create a random trie to copy
|
|
||||||
srcDb, srcTrie, srcData := makeTestTrie() |
|
||||||
|
|
||||||
// Create a destination trie and sync with the scheduler
|
|
||||||
diskdb := db.NewMemDatabase() |
|
||||||
triedb := NewDatabase(diskdb) |
|
||||||
sched := NewSync(srcTrie.Hash(), diskdb, nil) |
|
||||||
|
|
||||||
queue := make(map[common.Hash]struct{}) |
|
||||||
for _, hash := range sched.Missing(10000) { |
|
||||||
queue[hash] = struct{}{} |
|
||||||
} |
|
||||||
for len(queue) > 0 { |
|
||||||
// Sync only half of the scheduled nodes, even those in random order
|
|
||||||
results := make([]SyncResult, 0, len(queue)/2+1) |
|
||||||
for hash := range queue { |
|
||||||
data, err := srcDb.Node(hash) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) |
|
||||||
} |
|
||||||
results = append(results, SyncResult{hash, data}) |
|
||||||
|
|
||||||
if len(results) >= cap(results) { |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
// Feed the retrieved results back and queue new tasks
|
|
||||||
if _, index, err := sched.Process(results); err != nil { |
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err) |
|
||||||
} |
|
||||||
if index, err := sched.Commit(diskdb); err != nil { |
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err) |
|
||||||
} |
|
||||||
for _, result := range results { |
|
||||||
delete(queue, result.Hash) |
|
||||||
} |
|
||||||
for _, hash := range sched.Missing(10000) { |
|
||||||
queue[hash] = struct{}{} |
|
||||||
} |
|
||||||
} |
|
||||||
// Cross check that the two tries are in sync
|
|
||||||
checkTrieContents(t, triedb, srcTrie.Root(), srcData) |
|
||||||
} |
|
||||||
|
|
||||||
// Tests that a trie sync will not request nodes multiple times, even if they
|
|
||||||
// have such references.
|
|
||||||
func TestDuplicateAvoidanceSync(t *testing.T) { |
|
||||||
// Create a random trie to copy
|
|
||||||
srcDb, srcTrie, srcData := makeTestTrie() |
|
||||||
|
|
||||||
// Create a destination trie and sync with the scheduler
|
|
||||||
diskdb := db.NewMemDatabase() |
|
||||||
triedb := NewDatabase(diskdb) |
|
||||||
sched := NewSync(srcTrie.Hash(), diskdb, nil) |
|
||||||
|
|
||||||
queue := append([]common.Hash{}, sched.Missing(0)...) |
|
||||||
requested := make(map[common.Hash]struct{}) |
|
||||||
|
|
||||||
for len(queue) > 0 { |
|
||||||
results := make([]SyncResult, len(queue)) |
|
||||||
for i, hash := range queue { |
|
||||||
data, err := srcDb.Node(hash) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) |
|
||||||
} |
|
||||||
if _, ok := requested[hash]; ok { |
|
||||||
t.Errorf("hash %x already requested once", hash) |
|
||||||
} |
|
||||||
requested[hash] = struct{}{} |
|
||||||
|
|
||||||
results[i] = SyncResult{hash, data} |
|
||||||
} |
|
||||||
if _, index, err := sched.Process(results); err != nil { |
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err) |
|
||||||
} |
|
||||||
if index, err := sched.Commit(diskdb); err != nil { |
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err) |
|
||||||
} |
|
||||||
queue = append(queue[:0], sched.Missing(0)...) |
|
||||||
} |
|
||||||
// Cross check that the two tries are in sync
|
|
||||||
checkTrieContents(t, triedb, srcTrie.Root(), srcData) |
|
||||||
} |
|
||||||
|
|
||||||
// Tests that at any point in time during a sync, only complete sub-tries are in
|
|
||||||
// the database.
|
|
||||||
func TestIncompleteSync(t *testing.T) { |
|
||||||
// Create a random trie to copy
|
|
||||||
srcDb, srcTrie, _ := makeTestTrie() |
|
||||||
|
|
||||||
// Create a destination trie and sync with the scheduler
|
|
||||||
diskdb := db.NewMemDatabase() |
|
||||||
triedb := NewDatabase(diskdb) |
|
||||||
sched := NewSync(srcTrie.Hash(), diskdb, nil) |
|
||||||
|
|
||||||
added := []common.Hash{} |
|
||||||
queue := append([]common.Hash{}, sched.Missing(1)...) |
|
||||||
for len(queue) > 0 { |
|
||||||
// Fetch a batch of trie nodes
|
|
||||||
results := make([]SyncResult, len(queue)) |
|
||||||
for i, hash := range queue { |
|
||||||
data, err := srcDb.Node(hash) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("failed to retrieve node data for %x: %v", hash, err) |
|
||||||
} |
|
||||||
results[i] = SyncResult{hash, data} |
|
||||||
} |
|
||||||
// Process each of the trie nodes
|
|
||||||
if _, index, err := sched.Process(results); err != nil { |
|
||||||
t.Fatalf("failed to process result #%d: %v", index, err) |
|
||||||
} |
|
||||||
if index, err := sched.Commit(diskdb); err != nil { |
|
||||||
t.Fatalf("failed to commit data #%d: %v", index, err) |
|
||||||
} |
|
||||||
for _, result := range results { |
|
||||||
added = append(added, result.Hash) |
|
||||||
} |
|
||||||
// Check that all known sub-tries in the synced trie are complete
|
|
||||||
for _, root := range added { |
|
||||||
if err := checkTrieConsistency(triedb, root); err != nil { |
|
||||||
t.Fatalf("trie inconsistent: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
// Fetch the next batch to retrieve
|
|
||||||
queue = append(queue[:0], sched.Missing(1)...) |
|
||||||
} |
|
||||||
// Sanity check that removing any node from the database is detected
|
|
||||||
for _, node := range added[1:] { |
|
||||||
key := node.Bytes() |
|
||||||
value, _ := diskdb.Get(key) |
|
||||||
|
|
||||||
diskdb.Delete(key) |
|
||||||
if err := checkTrieConsistency(triedb, added[0]); err == nil { |
|
||||||
t.Fatalf("trie inconsistency not caught, missing: %x", key) |
|
||||||
} |
|
||||||
diskdb.Put(key, value) |
|
||||||
} |
|
||||||
} |
|
@ -1,474 +0,0 @@ |
|||||||
// Copyright 2014 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 trie implements Merkle Patricia Tries.
|
|
||||||
package trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"fmt" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/crypto" |
|
||||||
"github.com/ethereum/go-ethereum/log" |
|
||||||
"github.com/ethereum/go-ethereum/metrics" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
// emptyRoot is the known root hash of an empty trie.
|
|
||||||
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") |
|
||||||
|
|
||||||
// emptyState is the known hash of an empty state trie entry.
|
|
||||||
emptyState = crypto.Keccak256Hash(nil) |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
cacheMissCounter = metrics.NewRegisteredCounter("trie/cachemiss", nil) |
|
||||||
cacheUnloadCounter = metrics.NewRegisteredCounter("trie/cacheunload", nil) |
|
||||||
) |
|
||||||
|
|
||||||
// CacheMisses retrieves a global counter measuring the number of cache misses
|
|
||||||
// the trie had since process startup. This isn't useful for anything apart from
|
|
||||||
// trie debugging purposes.
|
|
||||||
func CacheMisses() int64 { |
|
||||||
return cacheMissCounter.Count() |
|
||||||
} |
|
||||||
|
|
||||||
// CacheUnloads retrieves a global counter measuring the number of cache unloads
|
|
||||||
// the trie did since process startup. This isn't useful for anything apart from
|
|
||||||
// trie debugging purposes.
|
|
||||||
func CacheUnloads() int64 { |
|
||||||
return cacheUnloadCounter.Count() |
|
||||||
} |
|
||||||
|
|
||||||
// LeafCallback is a callback type invoked when a trie operation reaches a leaf
|
|
||||||
// node. It's used by state sync and commit to allow handling external references
|
|
||||||
// between account and storage tries.
|
|
||||||
type LeafCallback func(leaf []byte, parent common.Hash) error |
|
||||||
|
|
||||||
// Trie is a Merkle Patricia Trie.
|
|
||||||
// The zero value is an empty trie with no database.
|
|
||||||
// Use New to create a trie that sits on top of a database.
|
|
||||||
//
|
|
||||||
// Trie is not safe for concurrent use.
|
|
||||||
type Trie struct { |
|
||||||
db *Database |
|
||||||
root node |
|
||||||
|
|
||||||
// Cache generation values.
|
|
||||||
// cachegen increases by one with each commit operation.
|
|
||||||
// new nodes are tagged with the current generation and unloaded
|
|
||||||
// when their generation is older than than cachegen-cachelimit.
|
|
||||||
cachegen, cachelimit uint16 |
|
||||||
} |
|
||||||
|
|
||||||
// SetCacheLimit sets the number of 'cache generations' to keep.
|
|
||||||
// A cache generation is created by a call to Commit.
|
|
||||||
func (t *Trie) SetCacheLimit(l uint16) { |
|
||||||
t.cachelimit = l |
|
||||||
} |
|
||||||
|
|
||||||
// newFlag returns the cache flag value for a newly created node.
|
|
||||||
func (t *Trie) newFlag() nodeFlag { |
|
||||||
return nodeFlag{dirty: true, gen: t.cachegen} |
|
||||||
} |
|
||||||
|
|
||||||
// New creates a trie with an existing root node from db.
|
|
||||||
//
|
|
||||||
// If root is the zero hash or the sha3 hash of an empty string, the
|
|
||||||
// trie is initially empty and does not require a database. Otherwise,
|
|
||||||
// New will panic if db is nil and returns a MissingNodeError if root does
|
|
||||||
// not exist in the database. Accessing the trie loads nodes from db on demand.
|
|
||||||
func New(root common.Hash, db *Database) (*Trie, error) { |
|
||||||
if db == nil { |
|
||||||
panic("trie.New called without a database") |
|
||||||
} |
|
||||||
trie := &Trie{ |
|
||||||
db: db, |
|
||||||
} |
|
||||||
if root != (common.Hash{}) && root != emptyRoot { |
|
||||||
rootnode, err := trie.resolveHash(root[:], nil) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
trie.root = rootnode |
|
||||||
} |
|
||||||
return trie, nil |
|
||||||
} |
|
||||||
|
|
||||||
// NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at
|
|
||||||
// the key after the given start key.
|
|
||||||
func (t *Trie) NodeIterator(start []byte) NodeIterator { |
|
||||||
return newNodeIterator(t, start) |
|
||||||
} |
|
||||||
|
|
||||||
// Get returns the value for key stored in the trie.
|
|
||||||
// The value bytes must not be modified by the caller.
|
|
||||||
func (t *Trie) Get(key []byte) []byte { |
|
||||||
res, err := t.TryGet(key) |
|
||||||
if err != nil { |
|
||||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) |
|
||||||
} |
|
||||||
return res |
|
||||||
} |
|
||||||
|
|
||||||
// TryGet returns the value for key stored in the trie.
|
|
||||||
// The value bytes must not be modified by the caller.
|
|
||||||
// If a node was not found in the database, a MissingNodeError is returned.
|
|
||||||
func (t *Trie) TryGet(key []byte) ([]byte, error) { |
|
||||||
key = keybytesToHex(key) |
|
||||||
value, newroot, didResolve, err := t.tryGet(t.root, key, 0) |
|
||||||
if err == nil && didResolve { |
|
||||||
t.root = newroot |
|
||||||
} |
|
||||||
return value, err |
|
||||||
} |
|
||||||
|
|
||||||
func (t *Trie) tryGet(origNode node, key []byte, pos int) (value []byte, newnode node, didResolve bool, err error) { |
|
||||||
switch n := (origNode).(type) { |
|
||||||
case nil: |
|
||||||
return nil, nil, false, nil |
|
||||||
case valueNode: |
|
||||||
return n, n, false, nil |
|
||||||
case *shortNode: |
|
||||||
if len(key)-pos < len(n.Key) || !bytes.Equal(n.Key, key[pos:pos+len(n.Key)]) { |
|
||||||
// key not found in trie
|
|
||||||
return nil, n, false, nil |
|
||||||
} |
|
||||||
value, newnode, didResolve, err = t.tryGet(n.Val, key, pos+len(n.Key)) |
|
||||||
if err == nil && didResolve { |
|
||||||
n = n.copy() |
|
||||||
n.Val = newnode |
|
||||||
n.flags.gen = t.cachegen |
|
||||||
} |
|
||||||
return value, n, didResolve, err |
|
||||||
case *fullNode: |
|
||||||
value, newnode, didResolve, err = t.tryGet(n.Children[key[pos]], key, pos+1) |
|
||||||
if err == nil && didResolve { |
|
||||||
n = n.copy() |
|
||||||
n.flags.gen = t.cachegen |
|
||||||
n.Children[key[pos]] = newnode |
|
||||||
} |
|
||||||
return value, n, didResolve, err |
|
||||||
case hashNode: |
|
||||||
child, err := t.resolveHash(n, key[:pos]) |
|
||||||
if err != nil { |
|
||||||
return nil, n, true, err |
|
||||||
} |
|
||||||
value, newnode, _, err := t.tryGet(child, key, pos) |
|
||||||
return value, newnode, true, err |
|
||||||
default: |
|
||||||
panic(fmt.Sprintf("%T: invalid node: %v", origNode, origNode)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Update associates key with value in the trie. Subsequent calls to
|
|
||||||
// Get will return value. If value has length zero, any existing value
|
|
||||||
// is deleted from the trie and calls to Get will return nil.
|
|
||||||
//
|
|
||||||
// The value bytes must not be modified by the caller while they are
|
|
||||||
// stored in the trie.
|
|
||||||
func (t *Trie) Update(key, value []byte) { |
|
||||||
if err := t.TryUpdate(key, value); err != nil { |
|
||||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// TryUpdate associates key with value in the trie. Subsequent calls to
|
|
||||||
// Get will return value. If value has length zero, any existing value
|
|
||||||
// is deleted from the trie and calls to Get will return nil.
|
|
||||||
//
|
|
||||||
// The value bytes must not be modified by the caller while they are
|
|
||||||
// stored in the trie.
|
|
||||||
//
|
|
||||||
// If a node was not found in the database, a MissingNodeError is returned.
|
|
||||||
func (t *Trie) TryUpdate(key, value []byte) error { |
|
||||||
k := keybytesToHex(key) |
|
||||||
if len(value) != 0 { |
|
||||||
_, n, err := t.insert(t.root, nil, k, valueNode(value)) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
t.root = n |
|
||||||
} else { |
|
||||||
_, n, err := t.delete(t.root, nil, k) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
t.root = n |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error) { |
|
||||||
if len(key) == 0 { |
|
||||||
if v, ok := n.(valueNode); ok { |
|
||||||
return !bytes.Equal(v, value.(valueNode)), value, nil |
|
||||||
} |
|
||||||
return true, value, nil |
|
||||||
} |
|
||||||
switch n := n.(type) { |
|
||||||
case *shortNode: |
|
||||||
matchlen := prefixLen(key, n.Key) |
|
||||||
// If the whole key matches, keep this short node as is
|
|
||||||
// and only update the value.
|
|
||||||
if matchlen == len(n.Key) { |
|
||||||
dirty, nn, err := t.insert(n.Val, append(prefix, key[:matchlen]...), key[matchlen:], value) |
|
||||||
if !dirty || err != nil { |
|
||||||
return false, n, err |
|
||||||
} |
|
||||||
return true, &shortNode{n.Key, nn, t.newFlag()}, nil |
|
||||||
} |
|
||||||
// Otherwise branch out at the index where they differ.
|
|
||||||
branch := &fullNode{flags: t.newFlag()} |
|
||||||
var err error |
|
||||||
_, branch.Children[n.Key[matchlen]], err = t.insert(nil, append(prefix, n.Key[:matchlen+1]...), n.Key[matchlen+1:], n.Val) |
|
||||||
if err != nil { |
|
||||||
return false, nil, err |
|
||||||
} |
|
||||||
_, branch.Children[key[matchlen]], err = t.insert(nil, append(prefix, key[:matchlen+1]...), key[matchlen+1:], value) |
|
||||||
if err != nil { |
|
||||||
return false, nil, err |
|
||||||
} |
|
||||||
// Replace this shortNode with the branch if it occurs at index 0.
|
|
||||||
if matchlen == 0 { |
|
||||||
return true, branch, nil |
|
||||||
} |
|
||||||
// Otherwise, replace it with a short node leading up to the branch.
|
|
||||||
return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil |
|
||||||
|
|
||||||
case *fullNode: |
|
||||||
dirty, nn, err := t.insert(n.Children[key[0]], append(prefix, key[0]), key[1:], value) |
|
||||||
if !dirty || err != nil { |
|
||||||
return false, n, err |
|
||||||
} |
|
||||||
n = n.copy() |
|
||||||
n.flags = t.newFlag() |
|
||||||
n.Children[key[0]] = nn |
|
||||||
return true, n, nil |
|
||||||
|
|
||||||
case nil: |
|
||||||
return true, &shortNode{key, value, t.newFlag()}, nil |
|
||||||
|
|
||||||
case hashNode: |
|
||||||
// We've hit a part of the trie that isn't loaded yet. Load
|
|
||||||
// the node and insert into it. This leaves all child nodes on
|
|
||||||
// the path to the value in the trie.
|
|
||||||
rn, err := t.resolveHash(n, prefix) |
|
||||||
if err != nil { |
|
||||||
return false, nil, err |
|
||||||
} |
|
||||||
dirty, nn, err := t.insert(rn, prefix, key, value) |
|
||||||
if !dirty || err != nil { |
|
||||||
return false, rn, err |
|
||||||
} |
|
||||||
return true, nn, nil |
|
||||||
|
|
||||||
default: |
|
||||||
panic(fmt.Sprintf("%T: invalid node: %v", n, n)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Delete removes any existing value for key from the trie.
|
|
||||||
func (t *Trie) Delete(key []byte) { |
|
||||||
if err := t.TryDelete(key); err != nil { |
|
||||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// TryDelete removes any existing value for key from the trie.
|
|
||||||
// If a node was not found in the database, a MissingNodeError is returned.
|
|
||||||
func (t *Trie) TryDelete(key []byte) error { |
|
||||||
k := keybytesToHex(key) |
|
||||||
_, n, err := t.delete(t.root, nil, k) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
t.root = n |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// delete returns the new root of the trie with key deleted.
|
|
||||||
// It reduces the trie to minimal form by simplifying
|
|
||||||
// nodes on the way up after deleting recursively.
|
|
||||||
func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) { |
|
||||||
switch n := n.(type) { |
|
||||||
case *shortNode: |
|
||||||
matchlen := prefixLen(key, n.Key) |
|
||||||
if matchlen < len(n.Key) { |
|
||||||
return false, n, nil // don't replace n on mismatch
|
|
||||||
} |
|
||||||
if matchlen == len(key) { |
|
||||||
return true, nil, nil // remove n entirely for whole matches
|
|
||||||
} |
|
||||||
// The key is longer than n.Key. Remove the remaining suffix
|
|
||||||
// from the subtrie. Child can never be nil here since the
|
|
||||||
// subtrie must contain at least two other values with keys
|
|
||||||
// longer than n.Key.
|
|
||||||
dirty, child, err := t.delete(n.Val, append(prefix, key[:len(n.Key)]...), key[len(n.Key):]) |
|
||||||
if !dirty || err != nil { |
|
||||||
return false, n, err |
|
||||||
} |
|
||||||
switch child := child.(type) { |
|
||||||
case *shortNode: |
|
||||||
// Deleting from the subtrie reduced it to another
|
|
||||||
// short node. Merge the nodes to avoid creating a
|
|
||||||
// shortNode{..., shortNode{...}}. Use concat (which
|
|
||||||
// always creates a new slice) instead of append to
|
|
||||||
// avoid modifying n.Key since it might be shared with
|
|
||||||
// other nodes.
|
|
||||||
return true, &shortNode{concat(n.Key, child.Key...), child.Val, t.newFlag()}, nil |
|
||||||
default: |
|
||||||
return true, &shortNode{n.Key, child, t.newFlag()}, nil |
|
||||||
} |
|
||||||
|
|
||||||
case *fullNode: |
|
||||||
dirty, nn, err := t.delete(n.Children[key[0]], append(prefix, key[0]), key[1:]) |
|
||||||
if !dirty || err != nil { |
|
||||||
return false, n, err |
|
||||||
} |
|
||||||
n = n.copy() |
|
||||||
n.flags = t.newFlag() |
|
||||||
n.Children[key[0]] = nn |
|
||||||
|
|
||||||
// Check how many non-nil entries are left after deleting and
|
|
||||||
// reduce the full node to a short node if only one entry is
|
|
||||||
// left. Since n must've contained at least two children
|
|
||||||
// before deletion (otherwise it would not be a full node) n
|
|
||||||
// can never be reduced to nil.
|
|
||||||
//
|
|
||||||
// When the loop is done, pos contains the index of the single
|
|
||||||
// value that is left in n or -2 if n contains at least two
|
|
||||||
// values.
|
|
||||||
pos := -1 |
|
||||||
for i, cld := range &n.Children { |
|
||||||
if cld != nil { |
|
||||||
if pos == -1 { |
|
||||||
pos = i |
|
||||||
} else { |
|
||||||
pos = -2 |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
if pos >= 0 { |
|
||||||
if pos != 16 { |
|
||||||
// If the remaining entry is a short node, it replaces
|
|
||||||
// n and its key gets the missing nibble tacked to the
|
|
||||||
// front. This avoids creating an invalid
|
|
||||||
// shortNode{..., shortNode{...}}. Since the entry
|
|
||||||
// might not be loaded yet, resolve it just for this
|
|
||||||
// check.
|
|
||||||
cnode, err := t.resolve(n.Children[pos], prefix) |
|
||||||
if err != nil { |
|
||||||
return false, nil, err |
|
||||||
} |
|
||||||
if cnode, ok := cnode.(*shortNode); ok { |
|
||||||
k := append([]byte{byte(pos)}, cnode.Key...) |
|
||||||
return true, &shortNode{k, cnode.Val, t.newFlag()}, nil |
|
||||||
} |
|
||||||
} |
|
||||||
// Otherwise, n is replaced by a one-nibble short node
|
|
||||||
// containing the child.
|
|
||||||
return true, &shortNode{[]byte{byte(pos)}, n.Children[pos], t.newFlag()}, nil |
|
||||||
} |
|
||||||
// n still contains at least two values and cannot be reduced.
|
|
||||||
return true, n, nil |
|
||||||
|
|
||||||
case valueNode: |
|
||||||
return true, nil, nil |
|
||||||
|
|
||||||
case nil: |
|
||||||
return false, nil, nil |
|
||||||
|
|
||||||
case hashNode: |
|
||||||
// We've hit a part of the trie that isn't loaded yet. Load
|
|
||||||
// the node and delete from it. This leaves all child nodes on
|
|
||||||
// the path to the value in the trie.
|
|
||||||
rn, err := t.resolveHash(n, prefix) |
|
||||||
if err != nil { |
|
||||||
return false, nil, err |
|
||||||
} |
|
||||||
dirty, nn, err := t.delete(rn, prefix, key) |
|
||||||
if !dirty || err != nil { |
|
||||||
return false, rn, err |
|
||||||
} |
|
||||||
return true, nn, nil |
|
||||||
|
|
||||||
default: |
|
||||||
panic(fmt.Sprintf("%T: invalid node: %v (%v)", n, n, key)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func concat(s1 []byte, s2 ...byte) []byte { |
|
||||||
r := make([]byte, len(s1)+len(s2)) |
|
||||||
copy(r, s1) |
|
||||||
copy(r[len(s1):], s2) |
|
||||||
return r |
|
||||||
} |
|
||||||
|
|
||||||
func (t *Trie) resolve(n node, prefix []byte) (node, error) { |
|
||||||
if n, ok := n.(hashNode); ok { |
|
||||||
return t.resolveHash(n, prefix) |
|
||||||
} |
|
||||||
return n, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) { |
|
||||||
cacheMissCounter.Inc(1) |
|
||||||
|
|
||||||
hash := common.BytesToHash(n) |
|
||||||
if node := t.db.node(hash, t.cachegen); node != nil { |
|
||||||
return node, nil |
|
||||||
} |
|
||||||
return nil, &MissingNodeError{NodeHash: hash, Path: prefix} |
|
||||||
} |
|
||||||
|
|
||||||
// Root returns the root hash of the trie.
|
|
||||||
// Deprecated: use Hash instead.
|
|
||||||
func (t *Trie) Root() []byte { return t.Hash().Bytes() } |
|
||||||
|
|
||||||
// Hash returns the root hash of the trie. It does not write to the
|
|
||||||
// database and can be used even if the trie doesn't have one.
|
|
||||||
func (t *Trie) Hash() common.Hash { |
|
||||||
hash, cached, _ := t.hashRoot(nil, nil) |
|
||||||
t.root = cached |
|
||||||
return common.BytesToHash(hash.(hashNode)) |
|
||||||
} |
|
||||||
|
|
||||||
// Commit writes all nodes to the trie's memory database, tracking the internal
|
|
||||||
// and external (for account tries) references.
|
|
||||||
func (t *Trie) Commit(onleaf LeafCallback) (root common.Hash, err error) { |
|
||||||
if t.db == nil { |
|
||||||
panic("commit called on trie with nil database") |
|
||||||
} |
|
||||||
hash, cached, err := t.hashRoot(t.db, onleaf) |
|
||||||
if err != nil { |
|
||||||
return common.Hash{}, err |
|
||||||
} |
|
||||||
t.root = cached |
|
||||||
t.cachegen++ |
|
||||||
return common.BytesToHash(hash.(hashNode)), nil |
|
||||||
} |
|
||||||
|
|
||||||
func (t *Trie) hashRoot(db *Database, onleaf LeafCallback) (node, node, error) { |
|
||||||
if t.root == nil { |
|
||||||
return hashNode(emptyRoot.Bytes()), nil, nil |
|
||||||
} |
|
||||||
h := newHasher(t.cachegen, t.cachelimit, onleaf) |
|
||||||
defer returnHasherToPool(h) |
|
||||||
return h.hash(t.root, db, true) |
|
||||||
} |
|
@ -1,615 +0,0 @@ |
|||||||
// Copyright 2014 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 trie |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"encoding/binary" |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"io/ioutil" |
|
||||||
"math/big" |
|
||||||
"math/rand" |
|
||||||
"os" |
|
||||||
"reflect" |
|
||||||
"testing" |
|
||||||
"testing/quick" |
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew" |
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/crypto" |
|
||||||
"github.com/ethereum/go-ethereum/rlp" |
|
||||||
"github.com/harmony-one/harmony/internal/db" |
|
||||||
) |
|
||||||
|
|
||||||
func init() { |
|
||||||
spew.Config.Indent = " " |
|
||||||
spew.Config.DisableMethods = false |
|
||||||
} |
|
||||||
|
|
||||||
// Used for testing
|
|
||||||
func newEmpty() *Trie { |
|
||||||
trie, _ := New(common.Hash{}, NewDatabase(db.NewMemDatabase())) |
|
||||||
return trie |
|
||||||
} |
|
||||||
|
|
||||||
func TestEmptyTrie(t *testing.T) { |
|
||||||
var trie Trie |
|
||||||
res := trie.Hash() |
|
||||||
exp := emptyRoot |
|
||||||
if res != common.Hash(exp) { |
|
||||||
t.Errorf("expected %x got %x", exp, res) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestNull(t *testing.T) { |
|
||||||
var trie Trie |
|
||||||
key := make([]byte, 32) |
|
||||||
value := []byte("test") |
|
||||||
trie.Update(key, value) |
|
||||||
if !bytes.Equal(trie.Get(key), value) { |
|
||||||
t.Fatal("wrong value") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestMissingRoot(t *testing.T) { |
|
||||||
trie, err := New(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), NewDatabase(db.NewMemDatabase())) |
|
||||||
if trie != nil { |
|
||||||
t.Error("New returned non-nil trie for invalid root") |
|
||||||
} |
|
||||||
if _, ok := err.(*MissingNodeError); !ok { |
|
||||||
t.Errorf("New returned wrong error: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestMissingNodeDisk(t *testing.T) { testMissingNode(t, false) } |
|
||||||
func TestMissingNodeMemonly(t *testing.T) { testMissingNode(t, true) } |
|
||||||
|
|
||||||
func testMissingNode(t *testing.T, memonly bool) { |
|
||||||
diskdb := db.NewMemDatabase() |
|
||||||
triedb := NewDatabase(diskdb) |
|
||||||
|
|
||||||
trie, _ := New(common.Hash{}, triedb) |
|
||||||
updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer") |
|
||||||
updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") |
|
||||||
root, _ := trie.Commit(nil) |
|
||||||
if !memonly { |
|
||||||
triedb.Commit(root, true) |
|
||||||
} |
|
||||||
|
|
||||||
trie, _ = New(root, triedb) |
|
||||||
_, err := trie.TryGet([]byte("120000")) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Unexpected error: %v", err) |
|
||||||
} |
|
||||||
trie, _ = New(root, triedb) |
|
||||||
_, err = trie.TryGet([]byte("120099")) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Unexpected error: %v", err) |
|
||||||
} |
|
||||||
trie, _ = New(root, triedb) |
|
||||||
_, err = trie.TryGet([]byte("123456")) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Unexpected error: %v", err) |
|
||||||
} |
|
||||||
trie, _ = New(root, triedb) |
|
||||||
err = trie.TryUpdate([]byte("120099"), []byte("zxcvzxcvzxcvzxcvzxcvzxcvzxcvzxcv")) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Unexpected error: %v", err) |
|
||||||
} |
|
||||||
trie, _ = New(root, triedb) |
|
||||||
err = trie.TryDelete([]byte("123456")) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Unexpected error: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
hash := common.HexToHash("0xe1d943cc8f061a0c0b98162830b970395ac9315654824bf21b73b891365262f9") |
|
||||||
if memonly { |
|
||||||
delete(triedb.nodes, hash) |
|
||||||
} else { |
|
||||||
diskdb.Delete(hash[:]) |
|
||||||
} |
|
||||||
|
|
||||||
trie, _ = New(root, triedb) |
|
||||||
_, err = trie.TryGet([]byte("120000")) |
|
||||||
if _, ok := err.(*MissingNodeError); !ok { |
|
||||||
t.Errorf("Wrong error: %v", err) |
|
||||||
} |
|
||||||
trie, _ = New(root, triedb) |
|
||||||
_, err = trie.TryGet([]byte("120099")) |
|
||||||
if _, ok := err.(*MissingNodeError); !ok { |
|
||||||
t.Errorf("Wrong error: %v", err) |
|
||||||
} |
|
||||||
trie, _ = New(root, triedb) |
|
||||||
_, err = trie.TryGet([]byte("123456")) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Unexpected error: %v", err) |
|
||||||
} |
|
||||||
trie, _ = New(root, triedb) |
|
||||||
err = trie.TryUpdate([]byte("120099"), []byte("zxcv")) |
|
||||||
if _, ok := err.(*MissingNodeError); !ok { |
|
||||||
t.Errorf("Wrong error: %v", err) |
|
||||||
} |
|
||||||
trie, _ = New(root, triedb) |
|
||||||
err = trie.TryDelete([]byte("123456")) |
|
||||||
if _, ok := err.(*MissingNodeError); !ok { |
|
||||||
t.Errorf("Wrong error: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestInsert(t *testing.T) { |
|
||||||
trie := newEmpty() |
|
||||||
|
|
||||||
updateString(trie, "doe", "reindeer") |
|
||||||
updateString(trie, "dog", "puppy") |
|
||||||
updateString(trie, "dogglesworth", "cat") |
|
||||||
|
|
||||||
exp := common.HexToHash("8aad789dff2f538bca5d8ea56e8abe10f4c7ba3a5dea95fea4cd6e7c3a1168d3") |
|
||||||
root := trie.Hash() |
|
||||||
if root != exp { |
|
||||||
t.Errorf("exp %x got %x", exp, root) |
|
||||||
} |
|
||||||
|
|
||||||
trie = newEmpty() |
|
||||||
updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") |
|
||||||
|
|
||||||
exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab") |
|
||||||
root, err := trie.Commit(nil) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("commit error: %v", err) |
|
||||||
} |
|
||||||
if root != exp { |
|
||||||
t.Errorf("exp %x got %x", exp, root) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestGet(t *testing.T) { |
|
||||||
trie := newEmpty() |
|
||||||
updateString(trie, "doe", "reindeer") |
|
||||||
updateString(trie, "dog", "puppy") |
|
||||||
updateString(trie, "dogglesworth", "cat") |
|
||||||
|
|
||||||
for i := 0; i < 2; i++ { |
|
||||||
res := getString(trie, "dog") |
|
||||||
if !bytes.Equal(res, []byte("puppy")) { |
|
||||||
t.Errorf("expected puppy got %x", res) |
|
||||||
} |
|
||||||
|
|
||||||
unknown := getString(trie, "unknown") |
|
||||||
if unknown != nil { |
|
||||||
t.Errorf("expected nil got %x", unknown) |
|
||||||
} |
|
||||||
|
|
||||||
if i == 1 { |
|
||||||
return |
|
||||||
} |
|
||||||
trie.Commit(nil) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestDelete(t *testing.T) { |
|
||||||
trie := newEmpty() |
|
||||||
vals := []struct{ k, v string }{ |
|
||||||
{"do", "verb"}, |
|
||||||
{"ether", "wookiedoo"}, |
|
||||||
{"horse", "stallion"}, |
|
||||||
{"shaman", "horse"}, |
|
||||||
{"doge", "coin"}, |
|
||||||
{"ether", ""}, |
|
||||||
{"dog", "puppy"}, |
|
||||||
{"shaman", ""}, |
|
||||||
} |
|
||||||
for _, val := range vals { |
|
||||||
if val.v != "" { |
|
||||||
updateString(trie, val.k, val.v) |
|
||||||
} else { |
|
||||||
deleteString(trie, val.k) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
hash := trie.Hash() |
|
||||||
exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84") |
|
||||||
if hash != exp { |
|
||||||
t.Errorf("expected %x got %x", exp, hash) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestEmptyValues(t *testing.T) { |
|
||||||
trie := newEmpty() |
|
||||||
|
|
||||||
vals := []struct{ k, v string }{ |
|
||||||
{"do", "verb"}, |
|
||||||
{"ether", "wookiedoo"}, |
|
||||||
{"horse", "stallion"}, |
|
||||||
{"shaman", "horse"}, |
|
||||||
{"doge", "coin"}, |
|
||||||
{"ether", ""}, |
|
||||||
{"dog", "puppy"}, |
|
||||||
{"shaman", ""}, |
|
||||||
} |
|
||||||
for _, val := range vals { |
|
||||||
updateString(trie, val.k, val.v) |
|
||||||
} |
|
||||||
|
|
||||||
hash := trie.Hash() |
|
||||||
exp := common.HexToHash("5991bb8c6514148a29db676a14ac506cd2cd5775ace63c30a4fe457715e9ac84") |
|
||||||
if hash != exp { |
|
||||||
t.Errorf("expected %x got %x", exp, hash) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestReplication(t *testing.T) { |
|
||||||
trie := newEmpty() |
|
||||||
vals := []struct{ k, v string }{ |
|
||||||
{"do", "verb"}, |
|
||||||
{"ether", "wookiedoo"}, |
|
||||||
{"horse", "stallion"}, |
|
||||||
{"shaman", "horse"}, |
|
||||||
{"doge", "coin"}, |
|
||||||
{"dog", "puppy"}, |
|
||||||
{"somethingveryoddindeedthis is", "myothernodedata"}, |
|
||||||
} |
|
||||||
for _, val := range vals { |
|
||||||
updateString(trie, val.k, val.v) |
|
||||||
} |
|
||||||
exp, err := trie.Commit(nil) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("commit error: %v", err) |
|
||||||
} |
|
||||||
|
|
||||||
// create a new trie on top of the database and check that lookups work.
|
|
||||||
trie2, err := New(exp, trie.db) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("can't recreate trie at %x: %v", exp, err) |
|
||||||
} |
|
||||||
for _, kv := range vals { |
|
||||||
if string(getString(trie2, kv.k)) != kv.v { |
|
||||||
t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v) |
|
||||||
} |
|
||||||
} |
|
||||||
hash, err := trie2.Commit(nil) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("commit error: %v", err) |
|
||||||
} |
|
||||||
if hash != exp { |
|
||||||
t.Errorf("root failure. expected %x got %x", exp, hash) |
|
||||||
} |
|
||||||
|
|
||||||
// perform some insertions on the new trie.
|
|
||||||
vals2 := []struct{ k, v string }{ |
|
||||||
{"do", "verb"}, |
|
||||||
{"ether", "wookiedoo"}, |
|
||||||
{"horse", "stallion"}, |
|
||||||
// {"shaman", "horse"},
|
|
||||||
// {"doge", "coin"},
|
|
||||||
// {"ether", ""},
|
|
||||||
// {"dog", "puppy"},
|
|
||||||
// {"somethingveryoddindeedthis is", "myothernodedata"},
|
|
||||||
// {"shaman", ""},
|
|
||||||
} |
|
||||||
for _, val := range vals2 { |
|
||||||
updateString(trie2, val.k, val.v) |
|
||||||
} |
|
||||||
if hash := trie2.Hash(); hash != exp { |
|
||||||
t.Errorf("root failure. expected %x got %x", exp, hash) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestLargeValue(t *testing.T) { |
|
||||||
trie := newEmpty() |
|
||||||
trie.Update([]byte("key1"), []byte{99, 99, 99, 99}) |
|
||||||
trie.Update([]byte("key2"), bytes.Repeat([]byte{1}, 32)) |
|
||||||
trie.Hash() |
|
||||||
} |
|
||||||
|
|
||||||
type countingDB struct { |
|
||||||
db.Database |
|
||||||
gets map[string]int |
|
||||||
} |
|
||||||
|
|
||||||
func (db *countingDB) Get(key []byte) ([]byte, error) { |
|
||||||
db.gets[string(key)]++ |
|
||||||
return db.Database.Get(key) |
|
||||||
} |
|
||||||
|
|
||||||
// TestCacheUnload checks that decoded nodes are unloaded after a
|
|
||||||
// certain number of commit operations.
|
|
||||||
func TestCacheUnload(t *testing.T) { |
|
||||||
// Create test trie with two branches.
|
|
||||||
trie := newEmpty() |
|
||||||
key1 := "---------------------------------" |
|
||||||
key2 := "---some other branch" |
|
||||||
updateString(trie, key1, "this is the branch of key1.") |
|
||||||
updateString(trie, key2, "this is the branch of key2.") |
|
||||||
|
|
||||||
root, _ := trie.Commit(nil) |
|
||||||
trie.db.Commit(root, true) |
|
||||||
|
|
||||||
// Commit the trie repeatedly and access key1.
|
|
||||||
// The branch containing it is loaded from DB exactly two times:
|
|
||||||
// in the 0th and 6th iteration.
|
|
||||||
db := &countingDB{Database: trie.db.diskdb, gets: make(map[string]int)} |
|
||||||
trie, _ = New(root, NewDatabase(db)) |
|
||||||
trie.SetCacheLimit(5) |
|
||||||
for i := 0; i < 12; i++ { |
|
||||||
getString(trie, key1) |
|
||||||
trie.Commit(nil) |
|
||||||
} |
|
||||||
// Check that it got loaded two times.
|
|
||||||
for dbkey, count := range db.gets { |
|
||||||
if count != 2 { |
|
||||||
t.Errorf("db key %x loaded %d times, want %d times", []byte(dbkey), count, 2) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// randTest performs random trie operations.
|
|
||||||
// Instances of this test are created by Generate.
|
|
||||||
type randTest []randTestStep |
|
||||||
|
|
||||||
type randTestStep struct { |
|
||||||
op int |
|
||||||
key []byte // for opUpdate, opDelete, opGet
|
|
||||||
value []byte // for opUpdate
|
|
||||||
err error // for debugging
|
|
||||||
} |
|
||||||
|
|
||||||
const ( |
|
||||||
opUpdate = iota |
|
||||||
opDelete |
|
||||||
opGet |
|
||||||
opCommit |
|
||||||
opHash |
|
||||||
opReset |
|
||||||
opItercheckhash |
|
||||||
opCheckCacheInvariant |
|
||||||
opMax // boundary value, not an actual op
|
|
||||||
) |
|
||||||
|
|
||||||
func (randTest) Generate(r *rand.Rand, size int) reflect.Value { |
|
||||||
var allKeys [][]byte |
|
||||||
genKey := func() []byte { |
|
||||||
if len(allKeys) < 2 || r.Intn(100) < 10 { |
|
||||||
// new key
|
|
||||||
key := make([]byte, r.Intn(50)) |
|
||||||
r.Read(key) |
|
||||||
allKeys = append(allKeys, key) |
|
||||||
return key |
|
||||||
} |
|
||||||
// use existing key
|
|
||||||
return allKeys[r.Intn(len(allKeys))] |
|
||||||
} |
|
||||||
|
|
||||||
var steps randTest |
|
||||||
for i := 0; i < size; i++ { |
|
||||||
step := randTestStep{op: r.Intn(opMax)} |
|
||||||
switch step.op { |
|
||||||
case opUpdate: |
|
||||||
step.key = genKey() |
|
||||||
step.value = make([]byte, 8) |
|
||||||
binary.BigEndian.PutUint64(step.value, uint64(i)) |
|
||||||
case opGet, opDelete: |
|
||||||
step.key = genKey() |
|
||||||
} |
|
||||||
steps = append(steps, step) |
|
||||||
} |
|
||||||
return reflect.ValueOf(steps) |
|
||||||
} |
|
||||||
|
|
||||||
func runRandTest(rt randTest) bool { |
|
||||||
triedb := NewDatabase(db.NewMemDatabase()) |
|
||||||
|
|
||||||
tr, _ := New(common.Hash{}, triedb) |
|
||||||
values := make(map[string]string) // tracks content of the trie
|
|
||||||
|
|
||||||
for i, step := range rt { |
|
||||||
switch step.op { |
|
||||||
case opUpdate: |
|
||||||
tr.Update(step.key, step.value) |
|
||||||
values[string(step.key)] = string(step.value) |
|
||||||
case opDelete: |
|
||||||
tr.Delete(step.key) |
|
||||||
delete(values, string(step.key)) |
|
||||||
case opGet: |
|
||||||
v := tr.Get(step.key) |
|
||||||
want := values[string(step.key)] |
|
||||||
if string(v) != want { |
|
||||||
rt[i].err = fmt.Errorf("mismatch for key 0x%x, got 0x%x want 0x%x", step.key, v, want) |
|
||||||
} |
|
||||||
case opCommit: |
|
||||||
_, rt[i].err = tr.Commit(nil) |
|
||||||
case opHash: |
|
||||||
tr.Hash() |
|
||||||
case opReset: |
|
||||||
hash, err := tr.Commit(nil) |
|
||||||
if err != nil { |
|
||||||
rt[i].err = err |
|
||||||
return false |
|
||||||
} |
|
||||||
newtr, err := New(hash, triedb) |
|
||||||
if err != nil { |
|
||||||
rt[i].err = err |
|
||||||
return false |
|
||||||
} |
|
||||||
tr = newtr |
|
||||||
case opItercheckhash: |
|
||||||
checktr, _ := New(common.Hash{}, triedb) |
|
||||||
it := NewIterator(tr.NodeIterator(nil)) |
|
||||||
for it.Next() { |
|
||||||
checktr.Update(it.Key, it.Value) |
|
||||||
} |
|
||||||
if tr.Hash() != checktr.Hash() { |
|
||||||
rt[i].err = fmt.Errorf("hash mismatch in opItercheckhash") |
|
||||||
} |
|
||||||
case opCheckCacheInvariant: |
|
||||||
rt[i].err = checkCacheInvariant(tr.root, nil, tr.cachegen, false, 0) |
|
||||||
} |
|
||||||
// Abort the test on error.
|
|
||||||
if rt[i].err != nil { |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
func checkCacheInvariant(n, parent node, parentCachegen uint16, parentDirty bool, depth int) error { |
|
||||||
var children []node |
|
||||||
var flag nodeFlag |
|
||||||
switch n := n.(type) { |
|
||||||
case *shortNode: |
|
||||||
flag = n.flags |
|
||||||
children = []node{n.Val} |
|
||||||
case *fullNode: |
|
||||||
flag = n.flags |
|
||||||
children = n.Children[:] |
|
||||||
default: |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
errorf := func(format string, args ...interface{}) error { |
|
||||||
msg := fmt.Sprintf(format, args...) |
|
||||||
msg += fmt.Sprintf("\nat depth %d node %s", depth, spew.Sdump(n)) |
|
||||||
msg += fmt.Sprintf("parent: %s", spew.Sdump(parent)) |
|
||||||
return errors.New(msg) |
|
||||||
} |
|
||||||
if flag.gen > parentCachegen { |
|
||||||
return errorf("cache invariant violation: %d > %d\n", flag.gen, parentCachegen) |
|
||||||
} |
|
||||||
if depth > 0 && !parentDirty && flag.dirty { |
|
||||||
return errorf("cache invariant violation: %d > %d\n", flag.gen, parentCachegen) |
|
||||||
} |
|
||||||
for _, child := range children { |
|
||||||
if err := checkCacheInvariant(child, n, flag.gen, flag.dirty, depth+1); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func TestRandom(t *testing.T) { |
|
||||||
if err := quick.Check(runRandTest, nil); err != nil { |
|
||||||
if cerr, ok := err.(*quick.CheckError); ok { |
|
||||||
t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In)) |
|
||||||
} |
|
||||||
t.Fatal(err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func BenchmarkGet(b *testing.B) { benchGet(b, false) } |
|
||||||
func BenchmarkGetDB(b *testing.B) { benchGet(b, true) } |
|
||||||
func BenchmarkUpdateBE(b *testing.B) { benchUpdate(b, binary.BigEndian) } |
|
||||||
func BenchmarkUpdateLE(b *testing.B) { benchUpdate(b, binary.LittleEndian) } |
|
||||||
|
|
||||||
const benchElemCount = 20000 |
|
||||||
|
|
||||||
func benchGet(b *testing.B, commit bool) { |
|
||||||
trie := new(Trie) |
|
||||||
if commit { |
|
||||||
_, tmpdb := tempDB() |
|
||||||
trie, _ = New(common.Hash{}, tmpdb) |
|
||||||
} |
|
||||||
k := make([]byte, 32) |
|
||||||
for i := 0; i < benchElemCount; i++ { |
|
||||||
binary.LittleEndian.PutUint64(k, uint64(i)) |
|
||||||
trie.Update(k, k) |
|
||||||
} |
|
||||||
binary.LittleEndian.PutUint64(k, benchElemCount/2) |
|
||||||
if commit { |
|
||||||
trie.Commit(nil) |
|
||||||
} |
|
||||||
|
|
||||||
b.ResetTimer() |
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
trie.Get(k) |
|
||||||
} |
|
||||||
b.StopTimer() |
|
||||||
|
|
||||||
if commit { |
|
||||||
ldb := trie.db.diskdb.(*db.LDBDatabase) |
|
||||||
ldb.Close() |
|
||||||
os.RemoveAll(ldb.Path()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie { |
|
||||||
trie := newEmpty() |
|
||||||
k := make([]byte, 32) |
|
||||||
for i := 0; i < b.N; i++ { |
|
||||||
e.PutUint64(k, uint64(i)) |
|
||||||
trie.Update(k, k) |
|
||||||
} |
|
||||||
return trie |
|
||||||
} |
|
||||||
|
|
||||||
// Benchmarks the trie hashing. Since the trie caches the result of any operation,
|
|
||||||
// we cannot use b.N as the number of hashing rouns, since all rounds apart from
|
|
||||||
// the first one will be NOOP. As such, we'll use b.N as the number of account to
|
|
||||||
// insert into the trie before measuring the hashing.
|
|
||||||
func BenchmarkHash(b *testing.B) { |
|
||||||
// Make the random benchmark deterministic
|
|
||||||
random := rand.New(rand.NewSource(0)) |
|
||||||
|
|
||||||
// Create a realistic account trie to hash
|
|
||||||
addresses := make([][20]byte, b.N) |
|
||||||
for i := 0; i < len(addresses); i++ { |
|
||||||
for j := 0; j < len(addresses[i]); j++ { |
|
||||||
addresses[i][j] = byte(random.Intn(256)) |
|
||||||
} |
|
||||||
} |
|
||||||
accounts := make([][]byte, len(addresses)) |
|
||||||
for i := 0; i < len(accounts); i++ { |
|
||||||
var ( |
|
||||||
nonce = uint64(random.Int63()) |
|
||||||
balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil)) |
|
||||||
root = emptyRoot |
|
||||||
code = crypto.Keccak256(nil) |
|
||||||
) |
|
||||||
accounts[i], _ = rlp.EncodeToBytes([]interface{}{nonce, balance, root, code}) |
|
||||||
} |
|
||||||
// Insert the accounts into the trie and hash it
|
|
||||||
trie := newEmpty() |
|
||||||
for i := 0; i < len(addresses); i++ { |
|
||||||
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i]) |
|
||||||
} |
|
||||||
b.ResetTimer() |
|
||||||
b.ReportAllocs() |
|
||||||
trie.Hash() |
|
||||||
} |
|
||||||
|
|
||||||
func tempDB() (string, *Database) { |
|
||||||
dir, err := ioutil.TempDir("", "trie-bench") |
|
||||||
if err != nil { |
|
||||||
panic(fmt.Sprintf("can't create temporary directory: %v", err)) |
|
||||||
} |
|
||||||
diskdb, err := db.NewLDBDatabase(dir, 256, 0) |
|
||||||
if err != nil { |
|
||||||
panic(fmt.Sprintf("can't create temporary database: %v", err)) |
|
||||||
} |
|
||||||
return dir, NewDatabase(diskdb) |
|
||||||
} |
|
||||||
|
|
||||||
func getString(trie *Trie, k string) []byte { |
|
||||||
return trie.Get([]byte(k)) |
|
||||||
} |
|
||||||
|
|
||||||
func updateString(trie *Trie, k, v string) { |
|
||||||
trie.Update([]byte(k), []byte(v)) |
|
||||||
} |
|
||||||
|
|
||||||
func deleteString(trie *Trie, k string) { |
|
||||||
trie.Delete([]byte(k)) |
|
||||||
} |
|
@ -1,11 +0,0 @@ |
|||||||
Contributors to log15: |
|
||||||
|
|
||||||
- Aaron L |
|
||||||
- Alan Shreve |
|
||||||
- Chris Hines |
|
||||||
- Ciaran Downey |
|
||||||
- Dmitry Chestnykh |
|
||||||
- Evan Shaw |
|
||||||
- Péter Szilágyi |
|
||||||
- Trevor Gattis |
|
||||||
- Vincent Vanackere |
|
@ -1,13 +0,0 @@ |
|||||||
Copyright 2014 Alan Shreve |
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||||
you may not use this file except in compliance with the License. |
|
||||||
You may obtain a copy of the License at |
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0 |
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software |
|
||||||
distributed under the License is distributed on an "AS IS" BASIS, |
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||||
See the License for the specific language governing permissions and |
|
||||||
limitations under the License. |
|
@ -1,77 +0,0 @@ |
|||||||
![obligatory xkcd](http://imgs.xkcd.com/comics/standards.png) |
|
||||||
|
|
||||||
# log15 [![godoc reference](https://godoc.org/github.com/inconshreveable/log15?status.png)](https://godoc.org/github.com/inconshreveable/log15) [![Build Status](https://travis-ci.org/inconshreveable/log15.svg?branch=master)](https://travis-ci.org/inconshreveable/log15) |
|
||||||
|
|
||||||
Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](http://golang.org/pkg/io/) and [`net/http`](http://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](http://golang.org/pkg/log/) package. |
|
||||||
|
|
||||||
## Features |
|
||||||
- A simple, easy-to-understand API |
|
||||||
- Promotes structured logging by encouraging use of key/value pairs |
|
||||||
- Child loggers which inherit and add their own private context |
|
||||||
- Lazy evaluation of expensive operations |
|
||||||
- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API. |
|
||||||
- Color terminal support |
|
||||||
- Built-in support for logging to files, streams, syslog, and the network |
|
||||||
- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more |
|
||||||
|
|
||||||
## Versioning |
|
||||||
The API of the master branch of log15 should always be considered unstable. If you want to rely on a stable API, |
|
||||||
you must vendor the library. |
|
||||||
|
|
||||||
## Importing |
|
||||||
|
|
||||||
```go |
|
||||||
import log "github.com/inconshreveable/log15" |
|
||||||
``` |
|
||||||
|
|
||||||
## Examples |
|
||||||
|
|
||||||
```go |
|
||||||
// all loggers can have key/value context |
|
||||||
srvlog := log.New("module", "app/server") |
|
||||||
|
|
||||||
// all log messages can have key/value context |
|
||||||
srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate) |
|
||||||
|
|
||||||
// child loggers with inherited context |
|
||||||
connlog := srvlog.New("raddr", c.RemoteAddr()) |
|
||||||
connlog.Info("connection open") |
|
||||||
|
|
||||||
// lazy evaluation |
|
||||||
connlog.Debug("ping remote", "latency", log.Lazy{pingRemote}) |
|
||||||
|
|
||||||
// flexible configuration |
|
||||||
srvlog.SetHandler(log.MultiHandler( |
|
||||||
log.StreamHandler(os.Stderr, log.LogfmtFormat()), |
|
||||||
log.LvlFilterHandler( |
|
||||||
log.LvlError, |
|
||||||
log.Must.FileHandler("errors.json", log.JSONFormat())))) |
|
||||||
``` |
|
||||||
|
|
||||||
Will result in output that looks like this: |
|
||||||
|
|
||||||
``` |
|
||||||
WARN[06-17|21:58:10] abnormal conn rate module=app/server rate=0.500 low=0.100 high=0.800 |
|
||||||
INFO[06-17|21:58:10] connection open module=app/server raddr=10.0.0.1 |
|
||||||
``` |
|
||||||
|
|
||||||
## Breaking API Changes |
|
||||||
The following commits broke API stability. This reference is intended to help you understand the consequences of updating to a newer version |
|
||||||
of log15. |
|
||||||
|
|
||||||
- 57a084d014d4150152b19e4e531399a7145d1540 - Added a `Get()` method to the `Logger` interface to retrieve the current handler |
|
||||||
- 93404652ee366648fa622b64d1e2b67d75a3094a - `Record` field `Call` changed to `stack.Call` with switch to `github.com/go-stack/stack` |
|
||||||
- a5e7613673c73281f58e15a87d2cf0cf111e8152 - Restored `syslog.Priority` argument to the `SyslogXxx` handler constructors |
|
||||||
|
|
||||||
## FAQ |
|
||||||
|
|
||||||
### The varargs style is brittle and error prone! Can I have type safety please? |
|
||||||
Yes. Use `log.Ctx`: |
|
||||||
|
|
||||||
```go |
|
||||||
srvlog := log.New(log.Ctx{"module": "app/server"}) |
|
||||||
srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate}) |
|
||||||
``` |
|
||||||
|
|
||||||
## License |
|
||||||
Apache |
|
@ -1,5 +0,0 @@ |
|||||||
This package is a fork of https://github.com/inconshreveable/log15, with some |
|
||||||
minor modifications required by the go-ethereum codebase: |
|
||||||
|
|
||||||
* Support for log level `trace` |
|
||||||
* Modified behavior to exit on `critical` failure |
|
@ -1,334 +0,0 @@ |
|||||||
package log |
|
||||||
|
|
||||||
/* |
|
||||||
Package log15 provides an opinionated, simple toolkit for best-practice logging that is |
|
||||||
both human and machine readable. It is modeled after the standard library's io and net/http |
|
||||||
packages. |
|
||||||
|
|
||||||
This package enforces you to only log key/value pairs. Keys must be strings. Values may be |
|
||||||
any type that you like. The default output format is logfmt, but you may also choose to use |
|
||||||
JSON instead if that suits you. Here's how you log: |
|
||||||
|
|
||||||
log.Info("page accessed", "path", r.URL.Path, "user_id", user.id) |
|
||||||
|
|
||||||
This will output a line that looks like: |
|
||||||
|
|
||||||
lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9 |
|
||||||
|
|
||||||
Getting Started |
|
||||||
|
|
||||||
To get started, you'll want to import the library: |
|
||||||
|
|
||||||
import log "github.com/inconshreveable/log15" |
|
||||||
|
|
||||||
|
|
||||||
Now you're ready to start logging: |
|
||||||
|
|
||||||
func main() { |
|
||||||
log.Info("Program starting", "args", os.Args()) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
Convention |
|
||||||
|
|
||||||
Because recording a human-meaningful message is common and good practice, the first argument to every |
|
||||||
logging method is the value to the *implicit* key 'msg'. |
|
||||||
|
|
||||||
Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so |
|
||||||
will the current timestamp with key 't'. |
|
||||||
|
|
||||||
You may supply any additional context as a set of key/value pairs to the logging function. log15 allows |
|
||||||
you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for |
|
||||||
logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate |
|
||||||
in the variadic argument list: |
|
||||||
|
|
||||||
log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val) |
|
||||||
|
|
||||||
If you really do favor your type-safety, you may choose to pass a log.Ctx instead: |
|
||||||
|
|
||||||
log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val}) |
|
||||||
|
|
||||||
|
|
||||||
Context loggers |
|
||||||
|
|
||||||
Frequently, you want to add context to a logger so that you can track actions associated with it. An http |
|
||||||
request is a good example. You can easily create new loggers that have context that is automatically included |
|
||||||
with each log line: |
|
||||||
|
|
||||||
requestlogger := log.New("path", r.URL.Path) |
|
||||||
|
|
||||||
// later
|
|
||||||
requestlogger.Debug("db txn commit", "duration", txnTimer.Finish()) |
|
||||||
|
|
||||||
This will output a log line that includes the path context that is attached to the logger: |
|
||||||
|
|
||||||
lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12 |
|
||||||
|
|
||||||
|
|
||||||
Handlers |
|
||||||
|
|
||||||
The Handler interface defines where log lines are printed to and how they are formated. Handler is a |
|
||||||
single interface that is inspired by net/http's handler interface: |
|
||||||
|
|
||||||
type Handler interface { |
|
||||||
Log(r *Record) error |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
Handlers can filter records, format them, or dispatch to multiple other Handlers. |
|
||||||
This package implements a number of Handlers for common logging patterns that are |
|
||||||
easily composed to create flexible, custom logging structures. |
|
||||||
|
|
||||||
Here's an example handler that prints logfmt output to Stdout: |
|
||||||
|
|
||||||
handler := log.StreamHandler(os.Stdout, log.LogfmtFormat()) |
|
||||||
|
|
||||||
Here's an example handler that defers to two other handlers. One handler only prints records |
|
||||||
from the rpc package in logfmt to standard out. The other prints records at Error level |
|
||||||
or above in JSON formatted output to the file /var/log/service.json |
|
||||||
|
|
||||||
handler := log.MultiHandler( |
|
||||||
log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JSONFormat())), |
|
||||||
log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler()) |
|
||||||
) |
|
||||||
|
|
||||||
Logging File Names and Line Numbers |
|
||||||
|
|
||||||
This package implements three Handlers that add debugging information to the |
|
||||||
context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's |
|
||||||
an example that adds the source file and line number of each logging call to |
|
||||||
the context. |
|
||||||
|
|
||||||
h := log.CallerFileHandler(log.StdoutHandler) |
|
||||||
log.Root().SetHandler(h) |
|
||||||
... |
|
||||||
log.Error("open file", "err", err) |
|
||||||
|
|
||||||
This will output a line that looks like: |
|
||||||
|
|
||||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42 |
|
||||||
|
|
||||||
Here's an example that logs the call stack rather than just the call site. |
|
||||||
|
|
||||||
h := log.CallerStackHandler("%+v", log.StdoutHandler) |
|
||||||
log.Root().SetHandler(h) |
|
||||||
... |
|
||||||
log.Error("open file", "err", err) |
|
||||||
|
|
||||||
This will output a line that looks like: |
|
||||||
|
|
||||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]" |
|
||||||
|
|
||||||
The "%+v" format instructs the handler to include the path of the source file |
|
||||||
relative to the compile time GOPATH. The github.com/go-stack/stack package |
|
||||||
documents the full list of formatting verbs and modifiers available. |
|
||||||
|
|
||||||
Custom Handlers |
|
||||||
|
|
||||||
The Handler interface is so simple that it's also trivial to write your own. Let's create an |
|
||||||
example handler which tries to write to one handler, but if that fails it falls back to |
|
||||||
writing to another handler and includes the error that it encountered when trying to write |
|
||||||
to the primary. This might be useful when trying to log over a network socket, but if that |
|
||||||
fails you want to log those records to a file on disk. |
|
||||||
|
|
||||||
type BackupHandler struct { |
|
||||||
Primary Handler |
|
||||||
Secondary Handler |
|
||||||
} |
|
||||||
|
|
||||||
func (h *BackupHandler) Log (r *Record) error { |
|
||||||
err := h.Primary.Log(r) |
|
||||||
if err != nil { |
|
||||||
r.Ctx = append(ctx, "primary_err", err) |
|
||||||
return h.Secondary.Log(r) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
This pattern is so useful that a generic version that handles an arbitrary number of Handlers |
|
||||||
is included as part of this library called FailoverHandler. |
|
||||||
|
|
||||||
Logging Expensive Operations |
|
||||||
|
|
||||||
Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay |
|
||||||
the price of computing them if you haven't turned up your logging level to a high level of detail. |
|
||||||
|
|
||||||
This package provides a simple type to annotate a logging operation that you want to be evaluated |
|
||||||
lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler |
|
||||||
filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example: |
|
||||||
|
|
||||||
func factorRSAKey() (factors []int) { |
|
||||||
// return the factors of a very large number
|
|
||||||
} |
|
||||||
|
|
||||||
log.Debug("factors", log.Lazy{factorRSAKey}) |
|
||||||
|
|
||||||
If this message is not logged for any reason (like logging at the Error level), then |
|
||||||
factorRSAKey is never evaluated. |
|
||||||
|
|
||||||
Dynamic context values |
|
||||||
|
|
||||||
The same log.Lazy mechanism can be used to attach context to a logger which you want to be |
|
||||||
evaluated when the message is logged, but not when the logger is created. For example, let's imagine |
|
||||||
a game where you have Player objects: |
|
||||||
|
|
||||||
type Player struct { |
|
||||||
name string |
|
||||||
alive bool |
|
||||||
log.Logger |
|
||||||
} |
|
||||||
|
|
||||||
You always want to log a player's name and whether they're alive or dead, so when you create the player |
|
||||||
object, you might do: |
|
||||||
|
|
||||||
p := &Player{name: name, alive: true} |
|
||||||
p.Logger = log.New("name", p.name, "alive", p.alive) |
|
||||||
|
|
||||||
Only now, even after a player has died, the logger will still report they are alive because the logging |
|
||||||
context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation |
|
||||||
of whether the player is alive or not to each log message, so that the log records will reflect the player's |
|
||||||
current state no matter when the log message is written: |
|
||||||
|
|
||||||
p := &Player{name: name, alive: true} |
|
||||||
isAlive := func() bool { return p.alive } |
|
||||||
player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive}) |
|
||||||
|
|
||||||
Terminal Format |
|
||||||
|
|
||||||
If log15 detects that stdout is a terminal, it will configure the default |
|
||||||
handler for it (which is log.StdoutHandler) to use TerminalFormat. This format |
|
||||||
logs records nicely for your terminal, including color-coded output based |
|
||||||
on log level. |
|
||||||
|
|
||||||
Error Handling |
|
||||||
|
|
||||||
Becasuse log15 allows you to step around the type system, there are a few ways you can specify |
|
||||||
invalid arguments to the logging functions. You could, for example, wrap something that is not |
|
||||||
a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries |
|
||||||
are typically the mechanism by which errors are reported, it would be onerous for the logging functions |
|
||||||
to return errors. Instead, log15 handles errors by making these guarantees to you: |
|
||||||
|
|
||||||
- Any log record containing an error will still be printed with the error explained to you as part of the log record. |
|
||||||
|
|
||||||
- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily |
|
||||||
(and if you like, automatically) detect if any of your logging calls are passing bad values. |
|
||||||
|
|
||||||
Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers |
|
||||||
are encouraged to return errors only if they fail to write their log records out to an external source like if the |
|
||||||
syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures |
|
||||||
like the FailoverHandler. |
|
||||||
|
|
||||||
Library Use |
|
||||||
|
|
||||||
log15 is intended to be useful for library authors as a way to provide configurable logging to |
|
||||||
users of their library. Best practice for use in a library is to always disable all output for your logger |
|
||||||
by default and to provide a public Logger instance that consumers of your library can configure. Like so: |
|
||||||
|
|
||||||
package yourlib |
|
||||||
|
|
||||||
import "github.com/inconshreveable/log15" |
|
||||||
|
|
||||||
var Log = log.New() |
|
||||||
|
|
||||||
func init() { |
|
||||||
Log.SetHandler(log.DiscardHandler()) |
|
||||||
} |
|
||||||
|
|
||||||
Users of your library may then enable it if they like: |
|
||||||
|
|
||||||
import "github.com/inconshreveable/log15" |
|
||||||
import "example.com/yourlib" |
|
||||||
|
|
||||||
func main() { |
|
||||||
handler := // custom handler setup
|
|
||||||
yourlib.Log.SetHandler(handler) |
|
||||||
} |
|
||||||
|
|
||||||
Best practices attaching logger context |
|
||||||
|
|
||||||
The ability to attach context to a logger is a powerful one. Where should you do it and why? |
|
||||||
I favor embedding a Logger directly into any persistent object in my application and adding |
|
||||||
unique, tracing context keys to it. For instance, imagine I am writing a web browser: |
|
||||||
|
|
||||||
type Tab struct { |
|
||||||
url string |
|
||||||
render *RenderingContext |
|
||||||
// ...
|
|
||||||
|
|
||||||
Logger |
|
||||||
} |
|
||||||
|
|
||||||
func NewTab(url string) *Tab { |
|
||||||
return &Tab { |
|
||||||
// ...
|
|
||||||
url: url, |
|
||||||
|
|
||||||
Logger: log.New("url", url), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
When a new tab is created, I assign a logger to it with the url of |
|
||||||
the tab as context so it can easily be traced through the logs. |
|
||||||
Now, whenever we perform any operation with the tab, we'll log with its |
|
||||||
embedded logger and it will include the tab title automatically: |
|
||||||
|
|
||||||
tab.Debug("moved position", "idx", tab.idx) |
|
||||||
|
|
||||||
There's only one problem. What if the tab url changes? We could |
|
||||||
use log.Lazy to make sure the current url is always written, but that |
|
||||||
would mean that we couldn't trace a tab's full lifetime through our |
|
||||||
logs after the user navigate to a new URL. |
|
||||||
|
|
||||||
Instead, think about what values to attach to your loggers the |
|
||||||
same way you think about what to use as a key in a SQL database schema. |
|
||||||
If it's possible to use a natural key that is unique for the lifetime of the |
|
||||||
object, do so. But otherwise, log15's ext package has a handy RandId |
|
||||||
function to let you generate what you might call "surrogate keys" |
|
||||||
They're just random hex identifiers to use for tracing. Back to our |
|
||||||
Tab example, we would prefer to set up our Logger like so: |
|
||||||
|
|
||||||
import logext "github.com/inconshreveable/log15/ext" |
|
||||||
|
|
||||||
t := &Tab { |
|
||||||
// ...
|
|
||||||
url: url, |
|
||||||
} |
|
||||||
|
|
||||||
t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl}) |
|
||||||
return t |
|
||||||
|
|
||||||
Now we'll have a unique traceable identifier even across loading new urls, but |
|
||||||
we'll still be able to see the tab's current url in the log messages. |
|
||||||
|
|
||||||
Must |
|
||||||
|
|
||||||
For all Handler functions which can return an error, there is a version of that |
|
||||||
function which will return no error but panics on failure. They are all available |
|
||||||
on the Must object. For example: |
|
||||||
|
|
||||||
log.Must.FileHandler("/path", log.JSONFormat) |
|
||||||
log.Must.NetHandler("tcp", ":1234", log.JSONFormat) |
|
||||||
|
|
||||||
Inspiration and Credit |
|
||||||
|
|
||||||
All of the following excellent projects inspired the design of this library: |
|
||||||
|
|
||||||
code.google.com/p/log4go |
|
||||||
|
|
||||||
github.com/op/go-logging |
|
||||||
|
|
||||||
github.com/technoweenie/grohl |
|
||||||
|
|
||||||
github.com/Sirupsen/logrus |
|
||||||
|
|
||||||
github.com/kr/logfmt |
|
||||||
|
|
||||||
github.com/spacemonkeygo/spacelog |
|
||||||
|
|
||||||
golang's stdlib, notably io and net/http |
|
||||||
|
|
||||||
The Name |
|
||||||
|
|
||||||
https://xkcd.com/927/
|
|
||||||
|
|
||||||
*/ |
|
@ -1,364 +0,0 @@ |
|||||||
package log |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"encoding/json" |
|
||||||
"fmt" |
|
||||||
"reflect" |
|
||||||
"strconv" |
|
||||||
"strings" |
|
||||||
"sync" |
|
||||||
"sync/atomic" |
|
||||||
"time" |
|
||||||
"unicode/utf8" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
timeFormat = "2006-01-02T15:04:05-0700" |
|
||||||
termTimeFormat = "01-02|15:04:05.999999" |
|
||||||
floatFormat = 'f' |
|
||||||
termMsgJust = 40 |
|
||||||
) |
|
||||||
|
|
||||||
// locationTrims are trimmed for display to avoid unwieldy log lines.
|
|
||||||
var locationTrims = []string{ |
|
||||||
"github.com/ethereum/go-ethereum/", |
|
||||||
} |
|
||||||
|
|
||||||
// PrintOrigins sets or unsets log location (file:line) printing for terminal
|
|
||||||
// format output.
|
|
||||||
func PrintOrigins(print bool) { |
|
||||||
if print { |
|
||||||
atomic.StoreUint32(&locationEnabled, 1) |
|
||||||
} else { |
|
||||||
atomic.StoreUint32(&locationEnabled, 0) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// locationEnabled is an atomic flag controlling whether the terminal formatter
|
|
||||||
// should append the log locations too when printing entries.
|
|
||||||
var locationEnabled uint32 |
|
||||||
|
|
||||||
// locationLength is the maxmimum path length encountered, which all logs are
|
|
||||||
// padded to to aid in alignment.
|
|
||||||
var locationLength uint32 |
|
||||||
|
|
||||||
// fieldPadding is a global map with maximum field value lengths seen until now
|
|
||||||
// to allow padding log contexts in a bit smarter way.
|
|
||||||
var fieldPadding = make(map[string]int) |
|
||||||
|
|
||||||
// fieldPaddingLock is a global mutex protecting the field padding map.
|
|
||||||
var fieldPaddingLock sync.RWMutex |
|
||||||
|
|
||||||
// Format interface.
|
|
||||||
type Format interface { |
|
||||||
Format(r *Record) []byte |
|
||||||
} |
|
||||||
|
|
||||||
// FormatFunc returns a new Format object which uses
|
|
||||||
// the given function to perform record formatting.
|
|
||||||
func FormatFunc(f func(*Record) []byte) Format { |
|
||||||
return formatFunc(f) |
|
||||||
} |
|
||||||
|
|
||||||
type formatFunc func(*Record) []byte |
|
||||||
|
|
||||||
func (f formatFunc) Format(r *Record) []byte { |
|
||||||
return f(r) |
|
||||||
} |
|
||||||
|
|
||||||
// TerminalStringer is an analogous interface to the stdlib stringer, allowing
|
|
||||||
// own types to have custom shortened serialization formats when printed to the
|
|
||||||
// screen.
|
|
||||||
type TerminalStringer interface { |
|
||||||
TerminalString() string |
|
||||||
} |
|
||||||
|
|
||||||
// TerminalFormat formats log records optimized for human readability on
|
|
||||||
// a terminal with color-coded level output and terser human friendly timestamp.
|
|
||||||
// This format should only be used for interactive programs or while developing.
|
|
||||||
//
|
|
||||||
// [TIME] [LEVEL] MESAGE key=value key=value ...
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002
|
|
||||||
//
|
|
||||||
func TerminalFormat(usecolor bool) Format { |
|
||||||
return FormatFunc(func(r *Record) []byte { |
|
||||||
var color = 0 |
|
||||||
if usecolor { |
|
||||||
switch r.Lvl { |
|
||||||
case LvlCrit: |
|
||||||
color = 35 |
|
||||||
case LvlError: |
|
||||||
color = 31 |
|
||||||
case LvlWarn: |
|
||||||
color = 33 |
|
||||||
case LvlInfo: |
|
||||||
color = 32 |
|
||||||
case LvlDebug: |
|
||||||
color = 36 |
|
||||||
case LvlTrace: |
|
||||||
color = 34 |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
b := &bytes.Buffer{} |
|
||||||
lvl := r.Lvl.AlignedString() |
|
||||||
if atomic.LoadUint32(&locationEnabled) != 0 { |
|
||||||
// Log origin printing was requested, format the location path and line number
|
|
||||||
location := fmt.Sprintf("%+v", r.Call) |
|
||||||
for _, prefix := range locationTrims { |
|
||||||
location = strings.TrimPrefix(location, prefix) |
|
||||||
} |
|
||||||
// Maintain the maximum location length for fancyer alignment
|
|
||||||
align := int(atomic.LoadUint32(&locationLength)) |
|
||||||
if align < len(location) { |
|
||||||
align = len(location) |
|
||||||
atomic.StoreUint32(&locationLength, uint32(align)) |
|
||||||
} |
|
||||||
padding := strings.Repeat(" ", align-len(location)) |
|
||||||
|
|
||||||
// Assemble and print the log heading
|
|
||||||
if color > 0 { |
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) |
|
||||||
} else { |
|
||||||
fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, r.Msg) |
|
||||||
} |
|
||||||
} else { |
|
||||||
if color > 0 { |
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg) |
|
||||||
} else { |
|
||||||
fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg) |
|
||||||
} |
|
||||||
} |
|
||||||
// try to justify the log output for short messages
|
|
||||||
length := utf8.RuneCountInString(r.Msg) |
|
||||||
if len(r.Ctx) > 0 && length < termMsgJust { |
|
||||||
b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length)) |
|
||||||
} |
|
||||||
// print the keys logfmt style
|
|
||||||
logfmt(b, r.Ctx, color, true) |
|
||||||
return b.Bytes() |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable
|
|
||||||
// format for key/value pairs.
|
|
||||||
//
|
|
||||||
// For more details see: http://godoc.org/github.com/kr/logfmt
|
|
||||||
//
|
|
||||||
func LogfmtFormat() Format { |
|
||||||
return FormatFunc(func(r *Record) []byte { |
|
||||||
common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} |
|
||||||
buf := &bytes.Buffer{} |
|
||||||
logfmt(buf, append(common, r.Ctx...), 0, false) |
|
||||||
return buf.Bytes() |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) { |
|
||||||
for i := 0; i < len(ctx); i += 2 { |
|
||||||
if i != 0 { |
|
||||||
buf.WriteByte(' ') |
|
||||||
} |
|
||||||
|
|
||||||
k, ok := ctx[i].(string) |
|
||||||
v := formatLogfmtValue(ctx[i+1], term) |
|
||||||
if !ok { |
|
||||||
k, v = errorKey, formatLogfmtValue(k, term) |
|
||||||
} |
|
||||||
|
|
||||||
// XXX: we should probably check that all of your key bytes aren't invalid
|
|
||||||
fieldPaddingLock.RLock() |
|
||||||
padding := fieldPadding[k] |
|
||||||
fieldPaddingLock.RUnlock() |
|
||||||
|
|
||||||
length := utf8.RuneCountInString(v) |
|
||||||
if padding < length { |
|
||||||
padding = length |
|
||||||
|
|
||||||
fieldPaddingLock.Lock() |
|
||||||
fieldPadding[k] = padding |
|
||||||
fieldPaddingLock.Unlock() |
|
||||||
} |
|
||||||
if color > 0 { |
|
||||||
fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, k) |
|
||||||
} else { |
|
||||||
buf.WriteString(k) |
|
||||||
buf.WriteByte('=') |
|
||||||
} |
|
||||||
buf.WriteString(v) |
|
||||||
if i < len(ctx)-2 { |
|
||||||
buf.Write(bytes.Repeat([]byte{' '}, padding-length)) |
|
||||||
} |
|
||||||
} |
|
||||||
buf.WriteByte('\n') |
|
||||||
} |
|
||||||
|
|
||||||
// JSONFormat formats log records as JSON objects separated by newlines.
|
|
||||||
// It is the equivalent of JSONFormatEx(false, true).
|
|
||||||
func JSONFormat() Format { |
|
||||||
return JSONFormatEx(false, true) |
|
||||||
} |
|
||||||
|
|
||||||
// JSONFormatEx formats log records as JSON objects. If pretty is true,
|
|
||||||
// records will be pretty-printed. If lineSeparated is true, records
|
|
||||||
// will be logged with a new line between each record.
|
|
||||||
func JSONFormatEx(pretty, lineSeparated bool) Format { |
|
||||||
jsonMarshal := json.Marshal |
|
||||||
if pretty { |
|
||||||
jsonMarshal = func(v interface{}) ([]byte, error) { |
|
||||||
return json.MarshalIndent(v, "", " ") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return FormatFunc(func(r *Record) []byte { |
|
||||||
props := make(map[string]interface{}) |
|
||||||
|
|
||||||
props[r.KeyNames.Time] = r.Time |
|
||||||
props[r.KeyNames.Lvl] = r.Lvl.String() |
|
||||||
props[r.KeyNames.Msg] = r.Msg |
|
||||||
|
|
||||||
for i := 0; i < len(r.Ctx); i += 2 { |
|
||||||
k, ok := r.Ctx[i].(string) |
|
||||||
if !ok { |
|
||||||
props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) |
|
||||||
} |
|
||||||
props[k] = formatJSONValue(r.Ctx[i+1]) |
|
||||||
} |
|
||||||
|
|
||||||
b, err := jsonMarshal(props) |
|
||||||
if err != nil { |
|
||||||
b, _ = jsonMarshal(map[string]string{ |
|
||||||
errorKey: err.Error(), |
|
||||||
}) |
|
||||||
return b |
|
||||||
} |
|
||||||
|
|
||||||
if lineSeparated { |
|
||||||
b = append(b, '\n') |
|
||||||
} |
|
||||||
|
|
||||||
return b |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func formatShared(value interface{}) (result interface{}) { |
|
||||||
defer func() { |
|
||||||
if err := recover(); err != nil { |
|
||||||
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { |
|
||||||
result = "nil" |
|
||||||
} else { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
} |
|
||||||
}() |
|
||||||
|
|
||||||
switch v := value.(type) { |
|
||||||
case time.Time: |
|
||||||
return v.Format(timeFormat) |
|
||||||
|
|
||||||
case error: |
|
||||||
return v.Error() |
|
||||||
|
|
||||||
case fmt.Stringer: |
|
||||||
return v.String() |
|
||||||
|
|
||||||
default: |
|
||||||
return v |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func formatJSONValue(value interface{}) interface{} { |
|
||||||
value = formatShared(value) |
|
||||||
switch value.(type) { |
|
||||||
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: |
|
||||||
return value |
|
||||||
default: |
|
||||||
return fmt.Sprintf("%+v", value) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// formatValue formats a value for serialization
|
|
||||||
func formatLogfmtValue(value interface{}, term bool) string { |
|
||||||
if value == nil { |
|
||||||
return "nil" |
|
||||||
} |
|
||||||
|
|
||||||
if t, ok := value.(time.Time); ok { |
|
||||||
// Performance optimization: No need for escaping since the provided
|
|
||||||
// timeFormat doesn't have any escape characters, and escaping is
|
|
||||||
// expensive.
|
|
||||||
return t.Format(timeFormat) |
|
||||||
} |
|
||||||
if term { |
|
||||||
if s, ok := value.(TerminalStringer); ok { |
|
||||||
// Custom terminal stringer provided, use that
|
|
||||||
return escapeString(s.TerminalString()) |
|
||||||
} |
|
||||||
} |
|
||||||
value = formatShared(value) |
|
||||||
switch v := value.(type) { |
|
||||||
case bool: |
|
||||||
return strconv.FormatBool(v) |
|
||||||
case float32: |
|
||||||
return strconv.FormatFloat(float64(v), floatFormat, 3, 64) |
|
||||||
case float64: |
|
||||||
return strconv.FormatFloat(v, floatFormat, 3, 64) |
|
||||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: |
|
||||||
return fmt.Sprintf("%d", value) |
|
||||||
case string: |
|
||||||
return escapeString(v) |
|
||||||
default: |
|
||||||
return escapeString(fmt.Sprintf("%+v", value)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
var stringBufPool = sync.Pool{ |
|
||||||
New: func() interface{} { return new(bytes.Buffer) }, |
|
||||||
} |
|
||||||
|
|
||||||
func escapeString(s string) string { |
|
||||||
needsQuotes := false |
|
||||||
needsEscape := false |
|
||||||
for _, r := range s { |
|
||||||
if r <= ' ' || r == '=' || r == '"' { |
|
||||||
needsQuotes = true |
|
||||||
} |
|
||||||
if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' { |
|
||||||
needsEscape = true |
|
||||||
} |
|
||||||
} |
|
||||||
if !needsEscape && !needsQuotes { |
|
||||||
return s |
|
||||||
} |
|
||||||
e := stringBufPool.Get().(*bytes.Buffer) |
|
||||||
e.WriteByte('"') |
|
||||||
for _, r := range s { |
|
||||||
switch r { |
|
||||||
case '\\', '"': |
|
||||||
e.WriteByte('\\') |
|
||||||
e.WriteByte(byte(r)) |
|
||||||
case '\n': |
|
||||||
e.WriteString("\\n") |
|
||||||
case '\r': |
|
||||||
e.WriteString("\\r") |
|
||||||
case '\t': |
|
||||||
e.WriteString("\\t") |
|
||||||
default: |
|
||||||
e.WriteRune(r) |
|
||||||
} |
|
||||||
} |
|
||||||
e.WriteByte('"') |
|
||||||
var ret string |
|
||||||
if needsQuotes { |
|
||||||
ret = e.String() |
|
||||||
} else { |
|
||||||
ret = string(e.Bytes()[1 : e.Len()-1]) |
|
||||||
} |
|
||||||
e.Reset() |
|
||||||
stringBufPool.Put(e) |
|
||||||
return ret |
|
||||||
} |
|
@ -1,359 +0,0 @@ |
|||||||
package log |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"net" |
|
||||||
"os" |
|
||||||
"reflect" |
|
||||||
"sync" |
|
||||||
|
|
||||||
"github.com/go-stack/stack" |
|
||||||
) |
|
||||||
|
|
||||||
// Handler defines where and how log records are written.
|
|
||||||
// A Logger prints its log records by writing to a Handler.
|
|
||||||
// Handlers are composable, providing you great flexibility in combining
|
|
||||||
// them to achieve the logging structure that suits your applications.
|
|
||||||
type Handler interface { |
|
||||||
Log(r *Record) error |
|
||||||
} |
|
||||||
|
|
||||||
// FuncHandler returns a Handler that logs records with the given
|
|
||||||
// function.
|
|
||||||
func FuncHandler(fn func(r *Record) error) Handler { |
|
||||||
return funcHandler(fn) |
|
||||||
} |
|
||||||
|
|
||||||
type funcHandler func(r *Record) error |
|
||||||
|
|
||||||
func (h funcHandler) Log(r *Record) error { |
|
||||||
return h(r) |
|
||||||
} |
|
||||||
|
|
||||||
// StreamHandler writes log records to an io.Writer
|
|
||||||
// with the given format. StreamHandler can be used
|
|
||||||
// to easily begin writing log records to other
|
|
||||||
// outputs.
|
|
||||||
//
|
|
||||||
// StreamHandler wraps itself with LazyHandler and SyncHandler
|
|
||||||
// to evaluate Lazy objects and perform safe concurrent writes.
|
|
||||||
func StreamHandler(wr io.Writer, fmtr Format) Handler { |
|
||||||
h := FuncHandler(func(r *Record) error { |
|
||||||
_, err := wr.Write(fmtr.Format(r)) |
|
||||||
return err |
|
||||||
}) |
|
||||||
return LazyHandler(SyncHandler(h)) |
|
||||||
} |
|
||||||
|
|
||||||
// SyncHandler can be wrapped around a handler to guarantee that
|
|
||||||
// only a single Log operation can proceed at a time. It's necessary
|
|
||||||
// for thread-safe concurrent writes.
|
|
||||||
func SyncHandler(h Handler) Handler { |
|
||||||
var mu sync.Mutex |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
defer mu.Unlock() |
|
||||||
mu.Lock() |
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// FileHandler returns a handler which writes log records to the give file
|
|
||||||
// using the given format. If the path
|
|
||||||
// already exists, FileHandler will append to the given file. If it does not,
|
|
||||||
// FileHandler will create the file with mode 0644.
|
|
||||||
func FileHandler(path string, fmtr Format) (Handler, error) { |
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return closingHandler{f, StreamHandler(f, fmtr)}, nil |
|
||||||
} |
|
||||||
|
|
||||||
// NetHandler opens a socket to the given address and writes records
|
|
||||||
// over the connection.
|
|
||||||
func NetHandler(network, addr string, fmtr Format) (Handler, error) { |
|
||||||
conn, err := net.Dial(network, addr) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
return closingHandler{conn, StreamHandler(conn, fmtr)}, nil |
|
||||||
} |
|
||||||
|
|
||||||
// XXX: closingHandler is essentially unused at the moment
|
|
||||||
// it's meant for a future time when the Handler interface supports
|
|
||||||
// a possible Close() operation
|
|
||||||
type closingHandler struct { |
|
||||||
io.WriteCloser |
|
||||||
Handler |
|
||||||
} |
|
||||||
|
|
||||||
func (h *closingHandler) Close() error { |
|
||||||
return h.WriteCloser.Close() |
|
||||||
} |
|
||||||
|
|
||||||
// CallerFileHandler returns a Handler that adds the line number and file of
|
|
||||||
// the calling function to the context with key "caller".
|
|
||||||
func CallerFileHandler(h Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call)) |
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// CallerFuncHandler returns a Handler that adds the calling function name to
|
|
||||||
// the context with key "fn".
|
|
||||||
func CallerFuncHandler(h Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
r.Ctx = append(r.Ctx, "fn", formatCall("%+n", r.Call)) |
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// This function is here to please go vet on Go < 1.8.
|
|
||||||
func formatCall(format string, c stack.Call) string { |
|
||||||
return fmt.Sprintf(format, c) |
|
||||||
} |
|
||||||
|
|
||||||
// CallerStackHandler returns a Handler that adds a stack trace to the context
|
|
||||||
// with key "stack". The stack trace is formated as a space separated list of
|
|
||||||
// call sites inside matching []'s. The most recent call site is listed first.
|
|
||||||
// Each call site is formatted according to format. See the documentation of
|
|
||||||
// package github.com/go-stack/stack for the list of supported formats.
|
|
||||||
func CallerStackHandler(format string, h Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
s := stack.Trace().TrimBelow(r.Call).TrimRuntime() |
|
||||||
if len(s) > 0 { |
|
||||||
r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s)) |
|
||||||
} |
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// FilterHandler returns a Handler that only writes records to the
|
|
||||||
// wrapped Handler if the given function evaluates true. For example,
|
|
||||||
// to only log records where the 'err' key is not nil:
|
|
||||||
//
|
|
||||||
// logger.SetHandler(FilterHandler(func(r *Record) bool {
|
|
||||||
// for i := 0; i < len(r.Ctx); i += 2 {
|
|
||||||
// if r.Ctx[i] == "err" {
|
|
||||||
// return r.Ctx[i+1] != nil
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return false
|
|
||||||
// }, h))
|
|
||||||
//
|
|
||||||
func FilterHandler(fn func(r *Record) bool, h Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
if fn(r) { |
|
||||||
return h.Log(r) |
|
||||||
} |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// MatchFilterHandler returns a Handler that only writes records
|
|
||||||
// to the wrapped Handler if the given key in the logged
|
|
||||||
// context matches the value. For example, to only log records
|
|
||||||
// from your ui package:
|
|
||||||
//
|
|
||||||
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
|
|
||||||
//
|
|
||||||
func MatchFilterHandler(key string, value interface{}, h Handler) Handler { |
|
||||||
return FilterHandler(func(r *Record) (pass bool) { |
|
||||||
switch key { |
|
||||||
case r.KeyNames.Lvl: |
|
||||||
return r.Lvl == value |
|
||||||
case r.KeyNames.Time: |
|
||||||
return r.Time == value |
|
||||||
case r.KeyNames.Msg: |
|
||||||
return r.Msg == value |
|
||||||
} |
|
||||||
|
|
||||||
for i := 0; i < len(r.Ctx); i += 2 { |
|
||||||
if r.Ctx[i] == key { |
|
||||||
return r.Ctx[i+1] == value |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
}, h) |
|
||||||
} |
|
||||||
|
|
||||||
// LvlFilterHandler returns a Handler that only writes
|
|
||||||
// records which are less than the given verbosity
|
|
||||||
// level to the wrapped Handler. For example, to only
|
|
||||||
// log Error/Crit records:
|
|
||||||
//
|
|
||||||
// log.LvlFilterHandler(log.LvlError, log.StdoutHandler)
|
|
||||||
//
|
|
||||||
func LvlFilterHandler(maxLvl Lvl, h Handler) Handler { |
|
||||||
return FilterHandler(func(r *Record) (pass bool) { |
|
||||||
return r.Lvl <= maxLvl |
|
||||||
}, h) |
|
||||||
} |
|
||||||
|
|
||||||
// MultiHandler dispatches any write to each of its handlers.
|
|
||||||
// This is useful for writing different types of log information
|
|
||||||
// to different locations. For example, to log to a file and
|
|
||||||
// standard error:
|
|
||||||
//
|
|
||||||
// log.MultiHandler(
|
|
||||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
|
||||||
// log.StderrHandler)
|
|
||||||
//
|
|
||||||
func MultiHandler(hs ...Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
for _, h := range hs { |
|
||||||
// what to do about failures?
|
|
||||||
h.Log(r) |
|
||||||
} |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// FailoverHandler writes all log records to the first handler
|
|
||||||
// specified, but will failover and write to the second handler if
|
|
||||||
// the first handler has failed, and so on for all handlers specified.
|
|
||||||
// For example you might want to log to a network socket, but failover
|
|
||||||
// to writing to a file if the network fails, and then to
|
|
||||||
// standard out if the file write fails:
|
|
||||||
//
|
|
||||||
// log.FailoverHandler(
|
|
||||||
// log.Must.NetHandler("tcp", ":9090", log.JSONFormat()),
|
|
||||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
|
||||||
// log.StdoutHandler)
|
|
||||||
//
|
|
||||||
// All writes that do not go to the first handler will add context with keys of
|
|
||||||
// the form "failover_err_{idx}" which explain the error encountered while
|
|
||||||
// trying to write to the handlers before them in the list.
|
|
||||||
func FailoverHandler(hs ...Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
var err error |
|
||||||
for i, h := range hs { |
|
||||||
err = h.Log(r) |
|
||||||
if err == nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) |
|
||||||
} |
|
||||||
|
|
||||||
return err |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// ChannelHandler writes all records to the given channel.
|
|
||||||
// It blocks if the channel is full. Useful for async processing
|
|
||||||
// of log messages, it's used by BufferedHandler.
|
|
||||||
func ChannelHandler(recs chan<- *Record) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
recs <- r |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// BufferedHandler writes all records to a buffered
|
|
||||||
// channel of the given size which flushes into the wrapped
|
|
||||||
// handler whenever it is available for writing. Since these
|
|
||||||
// writes happen asynchronously, all writes to a BufferedHandler
|
|
||||||
// never return an error and any errors from the wrapped handler are ignored.
|
|
||||||
func BufferedHandler(bufSize int, h Handler) Handler { |
|
||||||
recs := make(chan *Record, bufSize) |
|
||||||
go func() { |
|
||||||
for m := range recs { |
|
||||||
_ = h.Log(m) |
|
||||||
} |
|
||||||
}() |
|
||||||
return ChannelHandler(recs) |
|
||||||
} |
|
||||||
|
|
||||||
// LazyHandler writes all values to the wrapped handler after evaluating
|
|
||||||
// any lazy functions in the record's context. It is already wrapped
|
|
||||||
// around StreamHandler and SyslogHandler in this library, you'll only need
|
|
||||||
// it if you write your own Handler.
|
|
||||||
func LazyHandler(h Handler) Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
// go through the values (odd indices) and reassign
|
|
||||||
// the values of any lazy fn to the result of its execution
|
|
||||||
hadErr := false |
|
||||||
for i := 1; i < len(r.Ctx); i += 2 { |
|
||||||
lz, ok := r.Ctx[i].(Lazy) |
|
||||||
if ok { |
|
||||||
v, err := evaluateLazy(lz) |
|
||||||
if err != nil { |
|
||||||
hadErr = true |
|
||||||
r.Ctx[i] = err |
|
||||||
} else { |
|
||||||
if cs, ok := v.(stack.CallStack); ok { |
|
||||||
v = cs.TrimBelow(r.Call).TrimRuntime() |
|
||||||
} |
|
||||||
r.Ctx[i] = v |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if hadErr { |
|
||||||
r.Ctx = append(r.Ctx, errorKey, "bad lazy") |
|
||||||
} |
|
||||||
|
|
||||||
return h.Log(r) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func evaluateLazy(lz Lazy) (interface{}, error) { |
|
||||||
t := reflect.TypeOf(lz.Fn) |
|
||||||
|
|
||||||
if t.Kind() != reflect.Func { |
|
||||||
return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) |
|
||||||
} |
|
||||||
|
|
||||||
if t.NumIn() > 0 { |
|
||||||
return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) |
|
||||||
} |
|
||||||
|
|
||||||
if t.NumOut() == 0 { |
|
||||||
return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) |
|
||||||
} |
|
||||||
|
|
||||||
value := reflect.ValueOf(lz.Fn) |
|
||||||
results := value.Call([]reflect.Value{}) |
|
||||||
if len(results) == 1 { |
|
||||||
return results[0].Interface(), nil |
|
||||||
} |
|
||||||
values := make([]interface{}, len(results)) |
|
||||||
for i, v := range results { |
|
||||||
values[i] = v.Interface() |
|
||||||
} |
|
||||||
return values, nil |
|
||||||
} |
|
||||||
|
|
||||||
// DiscardHandler reports success for all writes but does nothing.
|
|
||||||
// It is useful for dynamically disabling logging at runtime via
|
|
||||||
// a Logger's SetHandler method.
|
|
||||||
func DiscardHandler() Handler { |
|
||||||
return FuncHandler(func(r *Record) error { |
|
||||||
return nil |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// Must provides the following Handler creation functions
|
|
||||||
// which instead of returning an error parameter only return a Handler
|
|
||||||
// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
|
|
||||||
var Must muster |
|
||||||
|
|
||||||
func must(h Handler, err error) Handler { |
|
||||||
if err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
return h |
|
||||||
} |
|
||||||
|
|
||||||
type muster struct{} |
|
||||||
|
|
||||||
func (m muster) FileHandler(path string, fmtr Format) Handler { |
|
||||||
return must(FileHandler(path, fmtr)) |
|
||||||
} |
|
||||||
|
|
||||||
func (m muster) NetHandler(network, addr string, fmtr Format) Handler { |
|
||||||
return must(NetHandler(network, addr, fmtr)) |
|
||||||
} |
|
@ -1,227 +0,0 @@ |
|||||||
// Copyright 2017 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 log |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"regexp" |
|
||||||
"runtime" |
|
||||||
"strconv" |
|
||||||
"strings" |
|
||||||
"sync" |
|
||||||
"sync/atomic" |
|
||||||
) |
|
||||||
|
|
||||||
// errVmoduleSyntax is returned when a user vmodule pattern is invalid.
|
|
||||||
var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N") |
|
||||||
|
|
||||||
// errTraceSyntax is returned when a user backtrace pattern is invalid.
|
|
||||||
var errTraceSyntax = errors.New("expect file.go:234") |
|
||||||
|
|
||||||
// GlogHandler is a log handler that mimics the filtering features of Google's
|
|
||||||
// glog logger: setting global log levels; overriding with callsite pattern
|
|
||||||
// matches; and requesting backtraces at certain positions.
|
|
||||||
type GlogHandler struct { |
|
||||||
origin Handler // The origin handler this wraps
|
|
||||||
|
|
||||||
level uint32 // Current log level, atomically accessible
|
|
||||||
override uint32 // Flag whether overrides are used, atomically accessible
|
|
||||||
backtrace uint32 // Flag whether backtrace location is set
|
|
||||||
|
|
||||||
patterns []pattern // Current list of patterns to override with
|
|
||||||
siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations
|
|
||||||
location string // file:line location where to do a stackdump at
|
|
||||||
lock sync.RWMutex // Lock protecting the override pattern list
|
|
||||||
} |
|
||||||
|
|
||||||
// NewGlogHandler creates a new log handler with filtering functionality similar
|
|
||||||
// to Google's glog logger. The returned handler implements Handler.
|
|
||||||
func NewGlogHandler(h Handler) *GlogHandler { |
|
||||||
return &GlogHandler{ |
|
||||||
origin: h, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// pattern contains a filter for the Vmodule option, holding a verbosity level
|
|
||||||
// and a file pattern to match.
|
|
||||||
type pattern struct { |
|
||||||
pattern *regexp.Regexp |
|
||||||
level Lvl |
|
||||||
} |
|
||||||
|
|
||||||
// Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
|
|
||||||
// and source files can be raised using Vmodule.
|
|
||||||
func (h *GlogHandler) Verbosity(level Lvl) { |
|
||||||
atomic.StoreUint32(&h.level, uint32(level)) |
|
||||||
} |
|
||||||
|
|
||||||
// Vmodule sets the glog verbosity pattern.
|
|
||||||
//
|
|
||||||
// The syntax of the argument is a comma-separated list of pattern=N, where the
|
|
||||||
// pattern is a literal file name or "glob" pattern matching and N is a V level.
|
|
||||||
//
|
|
||||||
// For instance:
|
|
||||||
//
|
|
||||||
// pattern="gopher.go=3"
|
|
||||||
// sets the V level to 3 in all Go files named "gopher.go"
|
|
||||||
//
|
|
||||||
// pattern="foo=3"
|
|
||||||
// sets V to 3 in all files of any packages whose import path ends in "foo"
|
|
||||||
//
|
|
||||||
// pattern="foo/*=3"
|
|
||||||
// sets V to 3 in all files of any packages whose import path contains "foo"
|
|
||||||
func (h *GlogHandler) Vmodule(ruleset string) error { |
|
||||||
var filter []pattern |
|
||||||
for _, rule := range strings.Split(ruleset, ",") { |
|
||||||
// Empty strings such as from a trailing comma can be ignored
|
|
||||||
if len(rule) == 0 { |
|
||||||
continue |
|
||||||
} |
|
||||||
// Ensure we have a pattern = level filter rule
|
|
||||||
parts := strings.Split(rule, "=") |
|
||||||
if len(parts) != 2 { |
|
||||||
return errVmoduleSyntax |
|
||||||
} |
|
||||||
parts[0] = strings.TrimSpace(parts[0]) |
|
||||||
parts[1] = strings.TrimSpace(parts[1]) |
|
||||||
if len(parts[0]) == 0 || len(parts[1]) == 0 { |
|
||||||
return errVmoduleSyntax |
|
||||||
} |
|
||||||
// Parse the level and if correct, assemble the filter rule
|
|
||||||
level, err := strconv.Atoi(parts[1]) |
|
||||||
if err != nil { |
|
||||||
return errVmoduleSyntax |
|
||||||
} |
|
||||||
if level <= 0 { |
|
||||||
continue // Ignore. It's harmless but no point in paying the overhead.
|
|
||||||
} |
|
||||||
// Compile the rule pattern into a regular expression
|
|
||||||
matcher := ".*" |
|
||||||
for _, comp := range strings.Split(parts[0], "/") { |
|
||||||
if comp == "*" { |
|
||||||
matcher += "(/.*)?" |
|
||||||
} else if comp != "" { |
|
||||||
matcher += "/" + regexp.QuoteMeta(comp) |
|
||||||
} |
|
||||||
} |
|
||||||
if !strings.HasSuffix(parts[0], ".go") { |
|
||||||
matcher += "/[^/]+\\.go" |
|
||||||
} |
|
||||||
matcher = matcher + "$" |
|
||||||
|
|
||||||
re, _ := regexp.Compile(matcher) |
|
||||||
filter = append(filter, pattern{re, Lvl(level)}) |
|
||||||
} |
|
||||||
// Swap out the vmodule pattern for the new filter system
|
|
||||||
h.lock.Lock() |
|
||||||
defer h.lock.Unlock() |
|
||||||
|
|
||||||
h.patterns = filter |
|
||||||
h.siteCache = make(map[uintptr]Lvl) |
|
||||||
atomic.StoreUint32(&h.override, uint32(len(filter))) |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// BacktraceAt sets the glog backtrace location. When set to a file and line
|
|
||||||
// number holding a logging statement, a stack trace will be written to the Info
|
|
||||||
// log whenever execution hits that statement.
|
|
||||||
//
|
|
||||||
// Unlike with Vmodule, the ".go" must be present.
|
|
||||||
func (h *GlogHandler) BacktraceAt(location string) error { |
|
||||||
// Ensure the backtrace location contains two non-empty elements
|
|
||||||
parts := strings.Split(location, ":") |
|
||||||
if len(parts) != 2 { |
|
||||||
return errTraceSyntax |
|
||||||
} |
|
||||||
parts[0] = strings.TrimSpace(parts[0]) |
|
||||||
parts[1] = strings.TrimSpace(parts[1]) |
|
||||||
if len(parts[0]) == 0 || len(parts[1]) == 0 { |
|
||||||
return errTraceSyntax |
|
||||||
} |
|
||||||
// Ensure the .go prefix is present and the line is valid
|
|
||||||
if !strings.HasSuffix(parts[0], ".go") { |
|
||||||
return errTraceSyntax |
|
||||||
} |
|
||||||
if _, err := strconv.Atoi(parts[1]); err != nil { |
|
||||||
return errTraceSyntax |
|
||||||
} |
|
||||||
// All seems valid
|
|
||||||
h.lock.Lock() |
|
||||||
defer h.lock.Unlock() |
|
||||||
|
|
||||||
h.location = location |
|
||||||
atomic.StoreUint32(&h.backtrace, uint32(len(location))) |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Log implements Handler.Log, filtering a log record through the global, local
|
|
||||||
// and backtrace filters, finally emitting it if either allow it through.
|
|
||||||
func (h *GlogHandler) Log(r *Record) error { |
|
||||||
// If backtracing is requested, check whether this is the callsite
|
|
||||||
if atomic.LoadUint32(&h.backtrace) > 0 { |
|
||||||
// Everything below here is slow. Although we could cache the call sites the
|
|
||||||
// same way as for vmodule, backtracing is so rare it's not worth the extra
|
|
||||||
// complexity.
|
|
||||||
h.lock.RLock() |
|
||||||
match := h.location == r.Call.String() |
|
||||||
h.lock.RUnlock() |
|
||||||
|
|
||||||
if match { |
|
||||||
// Callsite matched, raise the log level to info and gather the stacks
|
|
||||||
r.Lvl = LvlInfo |
|
||||||
|
|
||||||
buf := make([]byte, 1024*1024) |
|
||||||
buf = buf[:runtime.Stack(buf, true)] |
|
||||||
r.Msg += "\n\n" + string(buf) |
|
||||||
} |
|
||||||
} |
|
||||||
// If the global log level allows, fast track logging
|
|
||||||
if atomic.LoadUint32(&h.level) >= uint32(r.Lvl) { |
|
||||||
return h.origin.Log(r) |
|
||||||
} |
|
||||||
// If no local overrides are present, fast track skipping
|
|
||||||
if atomic.LoadUint32(&h.override) == 0 { |
|
||||||
return nil |
|
||||||
} |
|
||||||
// Check callsite cache for previously calculated log levels
|
|
||||||
h.lock.RLock() |
|
||||||
lvl, ok := h.siteCache[r.Call.PC()] |
|
||||||
h.lock.RUnlock() |
|
||||||
|
|
||||||
// If we didn't cache the callsite yet, calculate it
|
|
||||||
if !ok { |
|
||||||
h.lock.Lock() |
|
||||||
for _, rule := range h.patterns { |
|
||||||
if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) { |
|
||||||
h.siteCache[r.Call.PC()], lvl, ok = rule.level, rule.level, true |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
// If no rule matched, remember to drop log the next time
|
|
||||||
if !ok { |
|
||||||
h.siteCache[r.Call.PC()] = 0 |
|
||||||
} |
|
||||||
h.lock.Unlock() |
|
||||||
} |
|
||||||
if lvl >= r.Lvl { |
|
||||||
return h.origin.Log(r) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
// +build !go1.4
|
|
||||||
|
|
||||||
package log |
|
||||||
|
|
||||||
import ( |
|
||||||
"sync/atomic" |
|
||||||
"unsafe" |
|
||||||
) |
|
||||||
|
|
||||||
// swapHandler wraps another handler that may be swapped out
|
|
||||||
// dynamically at runtime in a thread-safe fashion.
|
|
||||||
type swapHandler struct { |
|
||||||
handler unsafe.Pointer |
|
||||||
} |
|
||||||
|
|
||||||
func (h *swapHandler) Log(r *Record) error { |
|
||||||
return h.Get().Log(r) |
|
||||||
} |
|
||||||
|
|
||||||
func (h *swapHandler) Get() Handler { |
|
||||||
return *(*Handler)(atomic.LoadPointer(&h.handler)) |
|
||||||
} |
|
||||||
|
|
||||||
func (h *swapHandler) Swap(newHandler Handler) { |
|
||||||
atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler)) |
|
||||||
} |
|
@ -1,23 +0,0 @@ |
|||||||
// +build go1.4
|
|
||||||
|
|
||||||
package log |
|
||||||
|
|
||||||
import "sync/atomic" |
|
||||||
|
|
||||||
// swapHandler wraps another handler that may be swapped out
|
|
||||||
// dynamically at runtime in a thread-safe fashion.
|
|
||||||
type swapHandler struct { |
|
||||||
handler atomic.Value |
|
||||||
} |
|
||||||
|
|
||||||
func (h *swapHandler) Log(r *Record) error { |
|
||||||
return (*h.handler.Load().(*Handler)).Log(r) |
|
||||||
} |
|
||||||
|
|
||||||
func (h *swapHandler) Swap(newHandler Handler) { |
|
||||||
h.handler.Store(&newHandler) |
|
||||||
} |
|
||||||
|
|
||||||
func (h *swapHandler) Get() Handler { |
|
||||||
return *h.handler.Load().(*Handler) |
|
||||||
} |
|
@ -1,244 +0,0 @@ |
|||||||
package log |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/go-stack/stack" |
|
||||||
) |
|
||||||
|
|
||||||
const timeKey = "t" |
|
||||||
const lvlKey = "lvl" |
|
||||||
const msgKey = "msg" |
|
||||||
const errorKey = "LOG15_ERROR" |
|
||||||
const skipLevel = 2 |
|
||||||
|
|
||||||
// Lvl type.
|
|
||||||
type Lvl int |
|
||||||
|
|
||||||
// Constants.
|
|
||||||
const ( |
|
||||||
LvlCrit Lvl = iota |
|
||||||
LvlError |
|
||||||
LvlWarn |
|
||||||
LvlInfo |
|
||||||
LvlDebug |
|
||||||
LvlTrace |
|
||||||
) |
|
||||||
|
|
||||||
// AlignedString returns a 5-character string containing the name of a Lvl.
|
|
||||||
func (l Lvl) AlignedString() string { |
|
||||||
switch l { |
|
||||||
case LvlTrace: |
|
||||||
return "TRACE" |
|
||||||
case LvlDebug: |
|
||||||
return "DEBUG" |
|
||||||
case LvlInfo: |
|
||||||
return "INFO " |
|
||||||
case LvlWarn: |
|
||||||
return "WARN " |
|
||||||
case LvlError: |
|
||||||
return "ERROR" |
|
||||||
case LvlCrit: |
|
||||||
return "CRIT " |
|
||||||
default: |
|
||||||
panic("bad level") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Strings returns the name of a Lvl.
|
|
||||||
func (l Lvl) String() string { |
|
||||||
switch l { |
|
||||||
case LvlTrace: |
|
||||||
return "trce" |
|
||||||
case LvlDebug: |
|
||||||
return "dbug" |
|
||||||
case LvlInfo: |
|
||||||
return "info" |
|
||||||
case LvlWarn: |
|
||||||
return "warn" |
|
||||||
case LvlError: |
|
||||||
return "eror" |
|
||||||
case LvlCrit: |
|
||||||
return "crit" |
|
||||||
default: |
|
||||||
panic("bad level") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// LvlFromString returns the appropriate Lvl from a string name.
|
|
||||||
// Useful for parsing command line args and configuration files.
|
|
||||||
func LvlFromString(lvlString string) (Lvl, error) { |
|
||||||
switch lvlString { |
|
||||||
case "trace", "trce": |
|
||||||
return LvlTrace, nil |
|
||||||
case "debug", "dbug": |
|
||||||
return LvlDebug, nil |
|
||||||
case "info": |
|
||||||
return LvlInfo, nil |
|
||||||
case "warn": |
|
||||||
return LvlWarn, nil |
|
||||||
case "error", "eror": |
|
||||||
return LvlError, nil |
|
||||||
case "crit": |
|
||||||
return LvlCrit, nil |
|
||||||
default: |
|
||||||
return LvlDebug, fmt.Errorf("Unknown level: %v", lvlString) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// A Record is what a Logger asks its handler to write
|
|
||||||
type Record struct { |
|
||||||
Time time.Time |
|
||||||
Lvl Lvl |
|
||||||
Msg string |
|
||||||
Ctx []interface{} |
|
||||||
Call stack.Call |
|
||||||
KeyNames RecordKeyNames |
|
||||||
} |
|
||||||
|
|
||||||
// RecordKeyNames gets stored in a Record when the write function is executed.
|
|
||||||
type RecordKeyNames struct { |
|
||||||
Time string |
|
||||||
Msg string |
|
||||||
Lvl string |
|
||||||
} |
|
||||||
|
|
||||||
// A Logger writes key/value pairs to a Handler
|
|
||||||
type Logger interface { |
|
||||||
// New returns a new Logger that has this logger's context plus the given context
|
|
||||||
New(ctx ...interface{}) Logger |
|
||||||
|
|
||||||
// GetHandler gets the handler associated with the logger.
|
|
||||||
GetHandler() Handler |
|
||||||
|
|
||||||
// SetHandler updates the logger to write records to the specified handler.
|
|
||||||
SetHandler(h Handler) |
|
||||||
|
|
||||||
// Log a message at the given level with context key/value pairs
|
|
||||||
Trace(msg string, ctx ...interface{}) |
|
||||||
Debug(msg string, ctx ...interface{}) |
|
||||||
Info(msg string, ctx ...interface{}) |
|
||||||
Warn(msg string, ctx ...interface{}) |
|
||||||
Error(msg string, ctx ...interface{}) |
|
||||||
Crit(msg string, ctx ...interface{}) |
|
||||||
} |
|
||||||
|
|
||||||
type logger struct { |
|
||||||
ctx []interface{} |
|
||||||
h *swapHandler |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) { |
|
||||||
l.h.Log(&Record{ |
|
||||||
Time: time.Now(), |
|
||||||
Lvl: lvl, |
|
||||||
Msg: msg, |
|
||||||
Ctx: newContext(l.ctx, ctx), |
|
||||||
Call: stack.Caller(skip), |
|
||||||
KeyNames: RecordKeyNames{ |
|
||||||
Time: timeKey, |
|
||||||
Msg: msgKey, |
|
||||||
Lvl: lvlKey, |
|
||||||
}, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) New(ctx ...interface{}) Logger { |
|
||||||
child := &logger{newContext(l.ctx, ctx), new(swapHandler)} |
|
||||||
child.SetHandler(l.h) |
|
||||||
return child |
|
||||||
} |
|
||||||
|
|
||||||
func newContext(prefix []interface{}, suffix []interface{}) []interface{} { |
|
||||||
normalizedSuffix := normalize(suffix) |
|
||||||
newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) |
|
||||||
n := copy(newCtx, prefix) |
|
||||||
copy(newCtx[n:], normalizedSuffix) |
|
||||||
return newCtx |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) Trace(msg string, ctx ...interface{}) { |
|
||||||
l.write(msg, LvlTrace, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) Debug(msg string, ctx ...interface{}) { |
|
||||||
l.write(msg, LvlDebug, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) Info(msg string, ctx ...interface{}) { |
|
||||||
l.write(msg, LvlInfo, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) Warn(msg string, ctx ...interface{}) { |
|
||||||
l.write(msg, LvlWarn, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) Error(msg string, ctx ...interface{}) { |
|
||||||
l.write(msg, LvlError, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) Crit(msg string, ctx ...interface{}) { |
|
||||||
l.write(msg, LvlCrit, ctx, skipLevel) |
|
||||||
os.Exit(1) |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) GetHandler() Handler { |
|
||||||
return l.h.Get() |
|
||||||
} |
|
||||||
|
|
||||||
func (l *logger) SetHandler(h Handler) { |
|
||||||
l.h.Swap(h) |
|
||||||
} |
|
||||||
|
|
||||||
func normalize(ctx []interface{}) []interface{} { |
|
||||||
// if the caller passed a Ctx object, then expand it
|
|
||||||
if len(ctx) == 1 { |
|
||||||
if ctxMap, ok := ctx[0].(Ctx); ok { |
|
||||||
ctx = ctxMap.toArray() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// ctx needs to be even because it's a series of key/value pairs
|
|
||||||
// no one wants to check for errors on logging functions,
|
|
||||||
// so instead of erroring on bad input, we'll just make sure
|
|
||||||
// that things are the right length and users can fix bugs
|
|
||||||
// when they see the output looks wrong
|
|
||||||
if len(ctx)%2 != 0 { |
|
||||||
ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil") |
|
||||||
} |
|
||||||
|
|
||||||
return ctx |
|
||||||
} |
|
||||||
|
|
||||||
// Lazy allows you to defer calculation of a logged value that is expensive
|
|
||||||
// to compute until it is certain that it must be evaluated with the given filters.
|
|
||||||
//
|
|
||||||
// Lazy may also be used in conjunction with a Logger's New() function
|
|
||||||
// to generate a child logger which always reports the current value of changing
|
|
||||||
// state.
|
|
||||||
//
|
|
||||||
// You may wrap any function which takes no arguments to Lazy. It may return any
|
|
||||||
// number of values of any type.
|
|
||||||
type Lazy struct { |
|
||||||
Fn interface{} |
|
||||||
} |
|
||||||
|
|
||||||
// Ctx is a map of key/value pairs to pass as context to a log function
|
|
||||||
// Use this only if you really need greater safety around the arguments you pass
|
|
||||||
// to the logging functions.
|
|
||||||
type Ctx map[string]interface{} |
|
||||||
|
|
||||||
func (c Ctx) toArray() []interface{} { |
|
||||||
arr := make([]interface{}, len(c)*2) |
|
||||||
|
|
||||||
i := 0 |
|
||||||
for k, v := range c { |
|
||||||
arr[i] = k |
|
||||||
arr[i+1] = v |
|
||||||
i += 2 |
|
||||||
} |
|
||||||
|
|
||||||
return arr |
|
||||||
} |
|
@ -1,72 +0,0 @@ |
|||||||
package log |
|
||||||
|
|
||||||
import ( |
|
||||||
"os" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
root = &logger{[]interface{}{}, new(swapHandler)} |
|
||||||
// StdoutHandler handles stdout
|
|
||||||
StdoutHandler = StreamHandler(os.Stdout, TerminalFormat(false)) |
|
||||||
// StderrHandler handles stderr
|
|
||||||
StderrHandler = StreamHandler(os.Stderr, LogfmtFormat()) |
|
||||||
) |
|
||||||
|
|
||||||
func init() { |
|
||||||
root.SetHandler(StdoutHandler) |
|
||||||
} |
|
||||||
|
|
||||||
// New returns a new logger with the given context.
|
|
||||||
// New is a convenient alias for Root().New
|
|
||||||
func New(ctx ...interface{}) Logger { |
|
||||||
return root.New(ctx...) |
|
||||||
} |
|
||||||
|
|
||||||
// Root returns the root logger
|
|
||||||
func Root() Logger { |
|
||||||
return root |
|
||||||
} |
|
||||||
|
|
||||||
// The following functions bypass the exported logger methods (logger.Debug,
|
|
||||||
// etc.) to keep the call depth the same for all paths to logger.write so
|
|
||||||
// runtime.Caller(2) always refers to the call site in client code.
|
|
||||||
|
|
||||||
// Trace is a convenient alias for Root().Trace
|
|
||||||
func Trace(msg string, ctx ...interface{}) { |
|
||||||
root.write(msg, LvlTrace, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
// Debug is a convenient alias for Root().Debug
|
|
||||||
func Debug(msg string, ctx ...interface{}) { |
|
||||||
root.write(msg, LvlDebug, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
// Info is a convenient alias for Root().Info
|
|
||||||
func Info(msg string, ctx ...interface{}) { |
|
||||||
root.write(msg, LvlInfo, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
// Warn is a convenient alias for Root().Warn
|
|
||||||
func Warn(msg string, ctx ...interface{}) { |
|
||||||
root.write(msg, LvlWarn, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
// Error is a convenient alias for Root().Error
|
|
||||||
func Error(msg string, ctx ...interface{}) { |
|
||||||
root.write(msg, LvlError, ctx, skipLevel) |
|
||||||
} |
|
||||||
|
|
||||||
// Crit is a convenient alias for Root().Crit
|
|
||||||
func Crit(msg string, ctx ...interface{}) { |
|
||||||
root.write(msg, LvlCrit, ctx, skipLevel) |
|
||||||
os.Exit(1) |
|
||||||
} |
|
||||||
|
|
||||||
// Output is a convenient alias for write, allowing for the modification of
|
|
||||||
// the calldepth (number of stack frames to skip).
|
|
||||||
// calldepth influences the reported line number of the log message.
|
|
||||||
// A calldepth of zero reports the immediate caller of Output.
|
|
||||||
// Non-zero calldepth skips as many stack frames.
|
|
||||||
func Output(msg string, lvl Lvl, calldepth int, ctx ...interface{}) { |
|
||||||
root.write(msg, lvl, ctx, calldepth+skipLevel) |
|
||||||
} |
|
@ -1,57 +0,0 @@ |
|||||||
// +build !windows,!plan9
|
|
||||||
|
|
||||||
package log |
|
||||||
|
|
||||||
import ( |
|
||||||
"log/syslog" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
// SyslogHandler opens a connection to the system syslog daemon by calling
|
|
||||||
// syslog.New and writes all records to it.
|
|
||||||
func SyslogHandler(priority syslog.Priority, tag string, fmtr Format) (Handler, error) { |
|
||||||
wr, err := syslog.New(priority, tag) |
|
||||||
return sharedSyslog(fmtr, wr, err) |
|
||||||
} |
|
||||||
|
|
||||||
// SyslogNetHandler opens a connection to a log daemon over the network and writes
|
|
||||||
// all log records to it.
|
|
||||||
func SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) (Handler, error) { |
|
||||||
wr, err := syslog.Dial(net, addr, priority, tag) |
|
||||||
return sharedSyslog(fmtr, wr, err) |
|
||||||
} |
|
||||||
|
|
||||||
func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) { |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
h := FuncHandler(func(r *Record) error { |
|
||||||
var syslogFn = sysWr.Info |
|
||||||
switch r.Lvl { |
|
||||||
case LvlCrit: |
|
||||||
syslogFn = sysWr.Crit |
|
||||||
case LvlError: |
|
||||||
syslogFn = sysWr.Err |
|
||||||
case LvlWarn: |
|
||||||
syslogFn = sysWr.Warning |
|
||||||
case LvlInfo: |
|
||||||
syslogFn = sysWr.Info |
|
||||||
case LvlDebug: |
|
||||||
syslogFn = sysWr.Debug |
|
||||||
case LvlTrace: |
|
||||||
syslogFn = func(m string) error { return nil } // There's no syslog level for trace
|
|
||||||
} |
|
||||||
|
|
||||||
s := strings.TrimSpace(string(fmtr.Format(r))) |
|
||||||
return syslogFn(s) |
|
||||||
}) |
|
||||||
return LazyHandler(&closingHandler{sysWr, h}), nil |
|
||||||
} |
|
||||||
|
|
||||||
func (m muster) SyslogHandler(priority syslog.Priority, tag string, fmtr Format) Handler { |
|
||||||
return must(SyslogHandler(priority, tag, fmtr)) |
|
||||||
} |
|
||||||
|
|
||||||
func (m muster) SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) Handler { |
|
||||||
return must(SyslogNetHandler(net, addr, priority, tag, fmtr)) |
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
The MIT License (MIT) |
|
||||||
|
|
||||||
Copyright (c) 2014 Simon Eskildsen |
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||||
of this software and associated documentation files (the "Software"), to deal |
|
||||||
in the Software without restriction, including without limitation the rights |
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||||
copies of the Software, and to permit persons to whom the Software is |
|
||||||
furnished to do so, subject to the following conditions: |
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in |
|
||||||
all copies or substantial portions of the Software. |
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
||||||
THE SOFTWARE. |
|
@ -1,13 +0,0 @@ |
|||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2013 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 file.
|
|
||||||
|
|
||||||
// +build appengine
|
|
||||||
|
|
||||||
package term |
|
||||||
|
|
||||||
// IsTty always returns false on AppEngine.
|
|
||||||
func IsTty(fd uintptr) bool { |
|
||||||
return false |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2013 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 file.
|
|
||||||
// +build !appengine
|
|
||||||
|
|
||||||
package term |
|
||||||
|
|
||||||
import "syscall" |
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA |
|
||||||
|
|
||||||
// Termios is syscall.Termios.
|
|
||||||
type Termios syscall.Termios |
|
@ -1,18 +0,0 @@ |
|||||||
package term |
|
||||||
|
|
||||||
import ( |
|
||||||
"syscall" |
|
||||||
) |
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA |
|
||||||
|
|
||||||
// Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
|
|
||||||
type Termios struct { |
|
||||||
Iflag uint32 |
|
||||||
Oflag uint32 |
|
||||||
Cflag uint32 |
|
||||||
Lflag uint32 |
|
||||||
Cc [20]uint8 |
|
||||||
Ispeed uint32 |
|
||||||
Ospeed uint32 |
|
||||||
} |
|
@ -1,15 +0,0 @@ |
|||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2013 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 file.
|
|
||||||
|
|
||||||
// +build !appengine
|
|
||||||
|
|
||||||
package term |
|
||||||
|
|
||||||
import "syscall" |
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TCGETS |
|
||||||
|
|
||||||
// Termios ...
|
|
||||||
type Termios syscall.Termios |
|
@ -1,7 +0,0 @@ |
|||||||
package term |
|
||||||
|
|
||||||
import "syscall" |
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA |
|
||||||
|
|
||||||
type Termios syscall.Termios |
|
@ -1,20 +0,0 @@ |
|||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2011 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 file.
|
|
||||||
|
|
||||||
// +build linux,!appengine darwin freebsd openbsd netbsd
|
|
||||||
|
|
||||||
package term |
|
||||||
|
|
||||||
import ( |
|
||||||
"syscall" |
|
||||||
"unsafe" |
|
||||||
) |
|
||||||
|
|
||||||
// IsTty returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTty(fd uintptr) bool { |
|
||||||
var termios Termios |
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) |
|
||||||
return err == 0 |
|
||||||
} |
|
@ -1,7 +0,0 @@ |
|||||||
package term |
|
||||||
|
|
||||||
import "syscall" |
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA |
|
||||||
|
|
||||||
type Termios syscall.Termios |
|
@ -1,9 +0,0 @@ |
|||||||
package term |
|
||||||
|
|
||||||
import "golang.org/x/sys/unix" |
|
||||||
|
|
||||||
// IsTty returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTty(fd uintptr) bool { |
|
||||||
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETA) |
|
||||||
return err == nil |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2011 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 file.
|
|
||||||
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package term |
|
||||||
|
|
||||||
import ( |
|
||||||
"syscall" |
|
||||||
"unsafe" |
|
||||||
) |
|
||||||
|
|
||||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll") |
|
||||||
|
|
||||||
var ( |
|
||||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode") |
|
||||||
) |
|
||||||
|
|
||||||
// IsTty returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTty(fd uintptr) bool { |
|
||||||
var st uint32 |
|
||||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) |
|
||||||
return r != 0 && e == 0 |
|
||||||
} |
|
Loading…
Reference in new issue