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