package state
|
|
|
|
import (
|
|
"bytes"
|
|
"sort"
|
|
|
|
ac "github.com/tendermint/tendermint/account"
|
|
"github.com/tendermint/tendermint/binary"
|
|
. "github.com/tendermint/tendermint/common"
|
|
dbm "github.com/tendermint/tendermint/db"
|
|
"github.com/tendermint/tendermint/merkle"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
func makeStorage(db dbm.DB, root []byte) merkle.Tree {
|
|
storage := merkle.NewIAVLTree(
|
|
binary.BasicCodec,
|
|
binary.BasicCodec,
|
|
1024,
|
|
db,
|
|
)
|
|
storage.Load(root)
|
|
return storage
|
|
}
|
|
|
|
type BlockCache struct {
|
|
db dbm.DB
|
|
backend *State
|
|
accounts map[string]accountInfo
|
|
storages map[Tuple256]storageInfo
|
|
names map[string]nameInfo
|
|
}
|
|
|
|
func NewBlockCache(backend *State) *BlockCache {
|
|
return &BlockCache{
|
|
db: backend.DB,
|
|
backend: backend,
|
|
accounts: make(map[string]accountInfo),
|
|
storages: make(map[Tuple256]storageInfo),
|
|
names: make(map[string]nameInfo),
|
|
}
|
|
}
|
|
|
|
func (cache *BlockCache) State() *State {
|
|
return cache.backend
|
|
}
|
|
|
|
//-------------------------------------
|
|
// BlockCache.account
|
|
|
|
func (cache *BlockCache) GetAccount(addr []byte) *ac.Account {
|
|
acc, _, removed, _ := cache.accounts[string(addr)].unpack()
|
|
if removed {
|
|
return nil
|
|
} else if acc != nil {
|
|
return acc
|
|
} else {
|
|
acc = cache.backend.GetAccount(addr)
|
|
cache.accounts[string(addr)] = accountInfo{acc, nil, false, false}
|
|
return acc
|
|
}
|
|
}
|
|
|
|
func (cache *BlockCache) UpdateAccount(acc *ac.Account) {
|
|
addr := acc.Address
|
|
// SANITY CHECK
|
|
_, storage, removed, _ := cache.accounts[string(addr)].unpack()
|
|
if removed {
|
|
panic("UpdateAccount on a removed account")
|
|
}
|
|
// SANITY CHECK END
|
|
cache.accounts[string(addr)] = accountInfo{acc, storage, false, true}
|
|
}
|
|
|
|
func (cache *BlockCache) RemoveAccount(addr []byte) {
|
|
// SANITY CHECK
|
|
_, _, removed, _ := cache.accounts[string(addr)].unpack()
|
|
if removed {
|
|
panic("RemoveAccount on a removed account")
|
|
}
|
|
// SANITY CHECK END
|
|
cache.accounts[string(addr)] = accountInfo{nil, nil, true, false}
|
|
}
|
|
|
|
// BlockCache.account
|
|
//-------------------------------------
|
|
// BlockCache.storage
|
|
|
|
func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) {
|
|
// Check cache
|
|
info, ok := cache.storages[Tuple256{addr, key}]
|
|
if ok {
|
|
return info.value
|
|
}
|
|
|
|
// Get or load storage
|
|
acc, storage, removed, dirty := cache.accounts[string(addr.Postfix(20))].unpack()
|
|
if removed {
|
|
panic("GetStorage() on removed account")
|
|
}
|
|
if acc != nil && storage == nil {
|
|
storage = makeStorage(cache.db, acc.StorageRoot)
|
|
cache.accounts[string(addr.Postfix(20))] = accountInfo{acc, storage, false, dirty}
|
|
} else if acc == nil {
|
|
return Zero256
|
|
}
|
|
|
|
// Load and set cache
|
|
_, val_ := storage.Get(key.Bytes())
|
|
value = Zero256
|
|
if val_ != nil {
|
|
value = LeftPadWord256(val_.([]byte))
|
|
}
|
|
cache.storages[Tuple256{addr, key}] = storageInfo{value, false}
|
|
return value
|
|
}
|
|
|
|
// NOTE: Set value to zero to removed from the trie.
|
|
func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) {
|
|
_, _, removed, _ := cache.accounts[string(addr.Postfix(20))].unpack()
|
|
if removed {
|
|
panic("SetStorage() on a removed account")
|
|
}
|
|
cache.storages[Tuple256{addr, key}] = storageInfo{value, true}
|
|
}
|
|
|
|
// BlockCache.storage
|
|
//-------------------------------------
|
|
// BlockCache.names
|
|
|
|
func (cache *BlockCache) GetNameRegEntry(name string) *types.NameRegEntry {
|
|
entry, removed, _ := cache.names[name].unpack()
|
|
if removed {
|
|
return nil
|
|
} else if entry != nil {
|
|
return entry
|
|
} else {
|
|
entry = cache.backend.GetNameRegEntry(name)
|
|
cache.names[name] = nameInfo{entry, false, false}
|
|
return entry
|
|
}
|
|
}
|
|
|
|
func (cache *BlockCache) UpdateNameRegEntry(entry *types.NameRegEntry) {
|
|
name := entry.Name
|
|
// SANITY CHECK
|
|
_, removed, _ := cache.names[name].unpack()
|
|
if removed {
|
|
panic("UpdateNameRegEntry on a removed name")
|
|
}
|
|
// SANITY CHECK END
|
|
cache.names[name] = nameInfo{entry, false, true}
|
|
}
|
|
|
|
func (cache *BlockCache) RemoveNameRegEntry(name string) {
|
|
// SANITY CHECK
|
|
_, removed, _ := cache.names[name].unpack()
|
|
if removed {
|
|
panic("RemoveNameRegEntry on a removed entry")
|
|
}
|
|
// SANITY CHECK END
|
|
cache.names[name] = nameInfo{nil, true, false}
|
|
}
|
|
|
|
// BlockCache.names
|
|
//-------------------------------------
|
|
|
|
// CONTRACT the updates are in deterministic order.
|
|
func (cache *BlockCache) Sync() {
|
|
|
|
// Determine order for storage updates
|
|
// The address comes first so it'll be grouped.
|
|
storageKeys := make([]Tuple256, 0, len(cache.storages))
|
|
for keyTuple := range cache.storages {
|
|
storageKeys = append(storageKeys, keyTuple)
|
|
}
|
|
Tuple256Slice(storageKeys).Sort()
|
|
|
|
// Update storage for all account/key.
|
|
// Later we'll iterate over all the users and save storage + update storage root.
|
|
var (
|
|
curAddr Word256
|
|
curAcc *ac.Account
|
|
curAccRemoved bool
|
|
curStorage merkle.Tree
|
|
)
|
|
for _, storageKey := range storageKeys {
|
|
addr, key := Tuple256Split(storageKey)
|
|
if addr != curAddr || curAcc == nil {
|
|
acc, storage, removed, _ := cache.accounts[string(addr.Postfix(20))].unpack()
|
|
if storage == nil {
|
|
storage = makeStorage(cache.db, acc.StorageRoot)
|
|
}
|
|
curAddr = addr
|
|
curAcc = acc
|
|
curAccRemoved = removed
|
|
curStorage = storage
|
|
}
|
|
if curAccRemoved {
|
|
continue
|
|
}
|
|
value, dirty := cache.storages[storageKey].unpack()
|
|
if !dirty {
|
|
continue
|
|
}
|
|
if value.IsZero() {
|
|
curStorage.Remove(key.Bytes())
|
|
} else {
|
|
curStorage.Set(key.Bytes(), value.Bytes())
|
|
cache.accounts[string(addr.Postfix(20))] = accountInfo{curAcc, curStorage, false, true}
|
|
}
|
|
}
|
|
|
|
// Determine order for accounts
|
|
addrStrs := []string{}
|
|
for addrStr := range cache.accounts {
|
|
addrStrs = append(addrStrs, addrStr)
|
|
}
|
|
sort.Strings(addrStrs)
|
|
|
|
// Update or delete accounts.
|
|
for _, addrStr := range addrStrs {
|
|
acc, storage, removed, dirty := cache.accounts[addrStr].unpack()
|
|
if removed {
|
|
removed := cache.backend.RemoveAccount(acc.Address)
|
|
if !removed {
|
|
panic(Fmt("Could not remove account to be removed: %X", acc.Address))
|
|
}
|
|
} else {
|
|
if acc == nil {
|
|
continue
|
|
}
|
|
if storage != nil {
|
|
newStorageRoot := storage.Save()
|
|
if !bytes.Equal(newStorageRoot, acc.StorageRoot) {
|
|
acc.StorageRoot = newStorageRoot
|
|
dirty = true
|
|
}
|
|
}
|
|
if dirty {
|
|
cache.backend.UpdateAccount(acc)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine order for names
|
|
// note names may be of any length less than some limit
|
|
nameStrs := []string{}
|
|
for nameStr := range cache.names {
|
|
nameStrs = append(nameStrs, nameStr)
|
|
}
|
|
sort.Strings(nameStrs)
|
|
|
|
// Update or delete names.
|
|
for _, nameStr := range nameStrs {
|
|
entry, removed, dirty := cache.names[nameStr].unpack()
|
|
if removed {
|
|
removed := cache.backend.RemoveNameRegEntry(nameStr)
|
|
if !removed {
|
|
panic(Fmt("Could not remove namereg entry to be removed: %s", nameStr))
|
|
}
|
|
} else {
|
|
if entry == nil {
|
|
continue
|
|
}
|
|
if dirty {
|
|
cache.backend.UpdateNameRegEntry(entry)
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
type accountInfo struct {
|
|
account *ac.Account
|
|
storage merkle.Tree
|
|
removed bool
|
|
dirty bool
|
|
}
|
|
|
|
func (accInfo accountInfo) unpack() (*ac.Account, merkle.Tree, bool, bool) {
|
|
return accInfo.account, accInfo.storage, accInfo.removed, accInfo.dirty
|
|
}
|
|
|
|
type storageInfo struct {
|
|
value Word256
|
|
dirty bool
|
|
}
|
|
|
|
func (stjInfo storageInfo) unpack() (Word256, bool) {
|
|
return stjInfo.value, stjInfo.dirty
|
|
}
|
|
|
|
type nameInfo struct {
|
|
name *types.NameRegEntry
|
|
removed bool
|
|
dirty bool
|
|
}
|
|
|
|
func (nInfo nameInfo) unpack() (*types.NameRegEntry, bool, bool) {
|
|
return nInfo.name, nInfo.removed, nInfo.dirty
|
|
}
|