package state import ( ac "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" ptypes "github.com/tendermint/tendermint/permission/types" // for GlobalPermissionAddress ... "github.com/tendermint/tendermint/vm" "github.com/tendermint/tendermint/vm/sha3" ) type TxCache struct { backend *BlockCache accounts map[Word256]vmAccountInfo storages map[Tuple256]Word256 logs []*vm.Log } func NewTxCache(backend *BlockCache) *TxCache { return &TxCache{ backend: backend, accounts: make(map[Word256]vmAccountInfo), storages: make(map[Tuple256]Word256), logs: make([]*vm.Log, 0), } } //------------------------------------- // TxCache.account func (cache *TxCache) GetAccount(addr Word256) *vm.Account { acc, removed := vmUnpack(cache.accounts[addr]) if removed { return nil } else if acc == nil { acc2 := cache.backend.GetAccount(addr.Postfix(20)) if acc2 != nil { return toVMAccount(acc2) } } return acc } func (cache *TxCache) UpdateAccount(acc *vm.Account) { addr := acc.Address // SANITY CHECK _, removed := vmUnpack(cache.accounts[addr]) if removed { panic("UpdateAccount on a removed account") } // SANITY CHECK END cache.accounts[addr] = vmAccountInfo{acc, false} } func (cache *TxCache) RemoveAccount(acc *vm.Account) { addr := acc.Address // SANITY CHECK _, removed := vmUnpack(cache.accounts[addr]) if removed { panic("RemoveAccount on a removed account") } // SANITY CHECK END cache.accounts[addr] = vmAccountInfo{acc, true} } // Creates a 20 byte address and bumps the creator's nonce. func (cache *TxCache) CreateAccount(creator *vm.Account) *vm.Account { // Generate an address nonce := creator.Nonce creator.Nonce += 1 addr := LeftPadWord256(NewContractAddress(creator.Address.Postfix(20), int(nonce))) // Create account from address. account, removed := vmUnpack(cache.accounts[addr]) if removed || account == nil { account = &vm.Account{ Address: addr, Balance: 0, Code: nil, Nonce: 0, StorageRoot: Zero256, Permissions: cache.GetAccount(ptypes.GlobalPermissionsAddress256).Permissions, Other: nil, } cache.accounts[addr] = vmAccountInfo{account, false} return account } else { // NONCE HANDLING SANITY CHECK OR SHA3 IS BROKEN panic(Fmt("Could not create account, address already exists: %X", addr)) } } // TxCache.account //------------------------------------- // TxCache.storage func (cache *TxCache) GetStorage(addr Word256, key Word256) Word256 { // Check cache value, ok := cache.storages[Tuple256{addr, key}] if ok { return value } // Load from backend return cache.backend.GetStorage(addr, key) } // NOTE: Set value to zero to removed from the trie. func (cache *TxCache) SetStorage(addr Word256, key Word256, value Word256) { // SANITY CHECK _, removed := vmUnpack(cache.accounts[addr]) if removed { panic("SetStorage() on a removed account") } // SANITY CHECK END cache.storages[Tuple256{addr, key}] = value } // TxCache.storage //------------------------------------- // These updates do not have to be in deterministic order, // the backend is responsible for ordering updates. func (cache *TxCache) Sync() { // Remove or update storage for addrKey, value := range cache.storages { addr, key := Tuple256Split(addrKey) cache.backend.SetStorage(addr, key, value) } // Remove or update accounts for addr, accInfo := range cache.accounts { acc, removed := vmUnpack(accInfo) if removed { cache.backend.RemoveAccount(addr.Postfix(20)) } else { cache.backend.UpdateAccount(toStateAccount(acc)) } } // TODO support logs, add them to the cache somehow. } func (cache *TxCache) AddLog(log *vm.Log) { cache.logs = append(cache.logs, log) } //----------------------------------------------------------------------------- // Convenience function to return address of new contract func NewContractAddress(caller []byte, nonce int) []byte { temp := make([]byte, 32+8) copy(temp, caller) PutInt64BE(temp[32:], int64(nonce)) return sha3.Sha3(temp)[:20] } // Converts backend.Account to vm.Account struct. func toVMAccount(acc *ac.Account) *vm.Account { return &vm.Account{ Address: LeftPadWord256(acc.Address), Balance: acc.Balance, Code: acc.Code, // This is crazy. Nonce: int64(acc.Sequence), StorageRoot: LeftPadWord256(acc.StorageRoot), Permissions: acc.Permissions.Copy(), Other: acc.PubKey, } } // Converts vm.Account to backend.Account struct. func toStateAccount(acc *vm.Account) *ac.Account { pubKey, ok := acc.Other.(ac.PubKey) if !ok { pubKey = nil } var storageRoot []byte if acc.StorageRoot.IsZero() { storageRoot = nil } else { storageRoot = acc.StorageRoot.Bytes() } return &ac.Account{ Address: acc.Address.Postfix(20), PubKey: pubKey, Balance: acc.Balance, Code: acc.Code, Sequence: int(acc.Nonce), StorageRoot: storageRoot, Permissions: acc.Permissions, } } type vmAccountInfo struct { account *vm.Account removed bool } func vmUnpack(accInfo vmAccountInfo) (*vm.Account, bool) { return accInfo.account, accInfo.removed }