package state import ( "bytes" "sort" ac "github.com/tendermint/tendermint/account" "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/merkle" "github.com/tendermint/tendermint/vm" "github.com/tendermint/tendermint/vm/sha3" ) // Converts state.Account to vm.Account struct. func toVMAccount(acc *ac.Account) *vm.Account { return &vm.Account{ Address: vm.BytesToWord(acc.Address), Balance: acc.Balance, Code: acc.Code, // This is crazy. Nonce: uint64(acc.Sequence), StorageRoot: vm.BytesToWord(acc.StorageRoot), } } // Converts vm.Account to state.Account struct. func toStateAccount(acc *vm.Account) *ac.Account { return &ac.Account{ Address: acc.Address.Address(), Balance: acc.Balance, Code: acc.Code, Sequence: uint(acc.Nonce), StorageRoot: acc.StorageRoot.Bytes(), } } //----------------------------------------------------------------------------- type AccountInfo struct { account *vm.Account deleted bool } type VMAppState struct { state *State accounts map[string]AccountInfo storage map[string]vm.Word logs []*vm.Log } func NewVMAppState(state *State) *VMAppState { return &VMAppState{ state: state, accounts: make(map[string]AccountInfo), storage: make(map[string]vm.Word), logs: make([]*vm.Log, 0), } } func unpack(accInfo AccountInfo) (*vm.Account, bool) { return accInfo.account, accInfo.deleted } func (vas *VMAppState) GetAccount(addr vm.Word) (*vm.Account, error) { account, deleted := unpack(vas.accounts[addr.String()]) if deleted { return nil, Errorf("Account was deleted: %X", addr) } else if account != nil { return account, nil } else { acc := vas.state.GetAccount(addr.Address()) if acc == nil { return nil, Errorf("Invalid account addr: %X", addr) } return toVMAccount(acc), nil } } func (vas *VMAppState) UpdateAccount(account *vm.Account) error { accountInfo, ok := vas.accounts[account.Address.String()] if !ok { vas.accounts[account.Address.String()] = AccountInfo{account, false} return nil } account, deleted := unpack(accountInfo) if deleted { return Errorf("Account was deleted: %X", account.Address) } else { vas.accounts[account.Address.String()] = AccountInfo{account, false} return nil } } func (vas *VMAppState) DeleteAccount(account *vm.Account) error { accountInfo, ok := vas.accounts[account.Address.String()] if !ok { vas.accounts[account.Address.String()] = AccountInfo{account, true} return nil } account, deleted := unpack(accountInfo) if deleted { return Errorf("Account was already deleted: %X", account.Address) } else { vas.accounts[account.Address.String()] = AccountInfo{account, true} return nil } } // Creates a 20 byte address and bumps the creator's nonce. func (vas *VMAppState) CreateAccount(creator *vm.Account) (*vm.Account, error) { // Generate an address nonce := creator.Nonce creator.Nonce += 1 addr := vm.RightPadWord(NewContractAddress(creator.Address[:], nonce)) // Create account from address. account, deleted := unpack(vas.accounts[addr.String()]) if deleted || account == nil { account = &vm.Account{ Address: addr, Balance: 0, Code: nil, Nonce: 0, StorageRoot: vm.Zero, } vas.accounts[addr.String()] = AccountInfo{account, false} return account, nil } else { panic(Fmt("Could not create account, address already exists: %X", addr)) // return nil, Errorf("Account already exists: %X", addr) } } func (vas *VMAppState) GetStorage(addr vm.Word, key vm.Word) (vm.Word, error) { account, deleted := unpack(vas.accounts[addr.String()]) if account == nil { return vm.Zero, Errorf("Invalid account addr: %X", addr) } else if deleted { return vm.Zero, Errorf("Account was deleted: %X", addr) } value, ok := vas.storage[addr.String()+key.String()] if ok { return value, nil } else { return vm.Zero, nil } } // NOTE: Set value to zero to delete from the trie. func (vas *VMAppState) SetStorage(addr vm.Word, key vm.Word, value vm.Word) (bool, error) { account, deleted := unpack(vas.accounts[addr.String()]) if account == nil { return false, Errorf("Invalid account addr: %X", addr) } else if deleted { return false, Errorf("Account was deleted: %X", addr) } _, ok := vas.storage[addr.String()+key.String()] vas.storage[addr.String()+key.String()] = value return ok, nil } // CONTRACT the updates are in deterministic order. func (vas *VMAppState) Sync() { // Determine order for accounts addrStrs := []string{} for addrStr := range vas.accounts { addrStrs = append(addrStrs, addrStr) } sort.Strings(addrStrs) // Update or delete accounts. for _, addrStr := range addrStrs { account, deleted := unpack(vas.accounts[addrStr]) if deleted { removed := vas.state.RemoveAccount(account.Address.Address()) if !removed { panic(Fmt("Could not remove account to be deleted: %X", account.Address)) } } else { if account == nil { panic(Fmt("Account should not be nil for addr: %X", account.Address)) } vas.state.UpdateAccount(toStateAccount(account)) } } // Determine order for storage updates // The address comes first so it'll be grouped. storageKeyStrs := []string{} for keyStr := range vas.storage { storageKeyStrs = append(storageKeyStrs, keyStr) } sort.Strings(storageKeyStrs) // Update storage for all account/key. storage := merkle.NewIAVLTree( binary.BasicCodec, // TODO change binary.BasicCodec, // TODO change 1024, // TODO change. vas.state.DB, ) var currentAccount *vm.Account var deleted bool for _, storageKey := range storageKeyStrs { value := vas.storage[storageKey] addrKeyBytes := []byte(storageKey) addr := addrKeyBytes[:32] key := addrKeyBytes[32:] if currentAccount == nil || !bytes.Equal(currentAccount.Address[:], addr) { currentAccount, deleted = unpack(vas.accounts[string(addr)]) if deleted { continue } storageRoot := currentAccount.StorageRoot storage.Load(storageRoot.Bytes()) } if value.IsZero() { _, removed := storage.Remove(key) if !removed { panic(Fmt("Storage could not be removed for addr: %X @ %X", addr, key)) } } else { storage.Set(key, value) } } // TODO support logs, add them to the state somehow. } func (vas *VMAppState) AddLog(log *vm.Log) { vas.logs = append(vas.logs, log) } //----------------------------------------------------------------------------- // Convenience function to return address of new contract func NewContractAddress(caller []byte, nonce uint64) []byte { temp := make([]byte, 32+8) copy(temp, caller) vm.PutUint64(temp[32:], nonce) return sha3.Sha3(temp)[:20] }