diff --git a/account/account.go b/account/account.go index 3974413b6..5c8bfda30 100644 --- a/account/account.go +++ b/account/account.go @@ -30,10 +30,12 @@ func SignBytes(o Signable) []byte { // on the blockchain. // Serialized by binary.[read|write]Reflect type Account struct { - Address []byte - PubKey PubKey - Sequence uint - Balance uint64 + Address []byte + PubKey PubKey + Sequence uint + Balance uint64 + Code []byte // VM code + StorageRoot []byte // VM storage merkle root. } func (account *Account) Copy() *Account { @@ -42,7 +44,7 @@ func (account *Account) Copy() *Account { } func (account *Account) String() string { - return fmt.Sprintf("Account{%X:%v}", account.Address, account.PubKey) + return fmt.Sprintf("Account{%X:%v C:%v S:%X}", account.Address, account.PubKey, len(account.Code), account.StorageRoot) } func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) { diff --git a/block/tx.go b/block/tx.go index 6ea8a7503..2a85ac13c 100644 --- a/block/tx.go +++ b/block/tx.go @@ -26,7 +26,7 @@ Tx (Transaction) is an atomic operation on the ledger state. Account Txs: - SendTx Send coins to address - - CallTx Send a msg to a contract that runs in the vm + - CallTx Send a msg to a contract that runs in the vm Validation Txs: - BondTx New validator posts a bond diff --git a/common/byteslice.go b/common/byteslice.go index ebc9ae38a..da42db810 100644 --- a/common/byteslice.go +++ b/common/byteslice.go @@ -6,6 +6,15 @@ func Fingerprint(slice []byte) []byte { return fingerprint } +func IsZeros(slice []byte) bool { + for _, byt := range slice { + if byt != byte(0) { + return false + } + } + return true +} + func RightPadBytes(slice []byte, l int) []byte { if l < len(slice) { return slice diff --git a/state/state.go b/state/state.go index 8d2baf1c8..78a065e78 100644 --- a/state/state.go +++ b/state/state.go @@ -12,6 +12,7 @@ import ( . "github.com/tendermint/tendermint/common" dbm "github.com/tendermint/tendermint/db" "github.com/tendermint/tendermint/merkle" + "github.com/tendermint/tendermint/vm" ) var ( @@ -97,6 +98,12 @@ func (s *State) Save() { s.DB.Set(stateKey, buf.Bytes()) } +// NOTE: the valSets are not Copy()'d. +// See TODOs in Copy() below. +func (s *State) Reset(checkpoint *State) { + *s = *checkpoint +} + func (s *State) Copy() *State { return &State{ DB: s.DB, @@ -104,9 +111,9 @@ func (s *State) Copy() *State { LastBlockHash: s.LastBlockHash, LastBlockParts: s.LastBlockParts, LastBlockTime: s.LastBlockTime, - BondedValidators: s.BondedValidators.Copy(), - LastBondedValidators: s.LastBondedValidators.Copy(), - UnbondingValidators: s.UnbondingValidators.Copy(), + BondedValidators: s.BondedValidators.Copy(), // TODO remove need for Copy() here. + LastBondedValidators: s.LastBondedValidators.Copy(), // That is, make updates to the validator set + UnbondingValidators: s.UnbondingValidators.Copy(), // copy the valSet lazily. accounts: s.accounts.Copy(), validatorInfos: s.validatorInfos.Copy(), } @@ -268,7 +275,9 @@ func (s *State) ExecTx(tx_ blk.Tx, runCall bool) error { return nil case *blk.CallTx: - accounts := map[string]*account.Account{} + accounts := make(map[string]*account.Account) + + // validate input inAcc := s.GetAccount(tx.Input.Address) if inAcc == nil { return blk.ErrTxInvalidAddress @@ -284,7 +293,7 @@ func (s *State) ExecTx(tx_ blk.Tx, runCall bool) error { return err } - // validate output address + // validate output if len(tx.Address) != 20 { return blk.ErrTxInvalidAddress } @@ -292,19 +301,54 @@ func (s *State) ExecTx(tx_ blk.Tx, runCall bool) error { if outAcc == nil { return blk.ErrTxInvalidAddress } + if inTotal < tx.Fee { + return blk.ErrTxInsufficientFunds + } accounts[string(tx.Address)] = outAcc - inTotal -= tx.Fee + // Remember unmodified state in case call fails. + checkpoint := s.Copy() - // Good! Adjust accounts - s.AdjustByInputs(accounts, []*blk.TxInput{tx.Input}) - outAcc.Balance += inTotal + // Good! Adjust accounts and maybe actually run the tx. + value := inTotal - tx.Fee + s.AdjustByInputs(accounts, []*blk.TxInput{tx.Input}) // adjusts inAcc. + outAcc.Balance += value s.UpdateAccounts(accounts) if runCall { - // TODO: Run the contract call! - // TODO: refund some gas + appState := NewVMAppState(s) + params := vm.Params{ + BlockHeight: uint64(s.LastBlockHeight), + BlockHash: vm.BytesToWord(s.LastBlockHash), + BlockTime: s.LastBlockTime.Unix(), + GasLimit: 10000000, + } + caller := toVMAccount(inAcc) + callee := toVMAccount(outAcc) + appState.AddAccount(caller) // because we adjusted by input above. + appState.AddAccount(callee) // because we adjusted by input above. + vmach := vm.NewVM(appState, params, caller.Address) + gas := tx.GasLimit + ret, err_ := vmach.Call(caller, callee, outAcc.Code, tx.Data, value, &gas) + if err_ != nil { + // Failure + // Revert the state while charging gas for the user. + s.Reset(checkpoint) + callee.Balance -= tx.Fee + s.UpdateAccounts(accounts) + } else { + // Success + appState.Sync() + } + // Create a receipt from the ret and whether errored. + log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err_) + } else { + // The mempool does not call txs until + // the proposer determines the order of txs. + // So mempool will skip the actual .Call(), + // and only deduct from the caller's balance. } + return nil case *blk.BondTx: @@ -706,6 +750,11 @@ func (s *State) UpdateAccounts(accounts map[string]*account.Account) { } } +func (s *State) RemoveAccount(address []byte) bool { + _, removed := s.accounts.Remove(address) + return removed +} + // The returned ValidatorInfo is a copy, so mutating it // has no side effects. func (s *State) GetValidatorInfo(address []byte) *ValidatorInfo { diff --git a/state/vm_app_state.go b/state/vm_app_state.go new file mode 100644 index 000000000..247e42c4f --- /dev/null +++ b/state/vm_app_state.go @@ -0,0 +1,217 @@ +package state + +import ( + "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" +) + +// 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 +} + +// Used to add the origin of the tx to VMAppState. +func (vas *VMAppState) AddAccount(account *vm.Account) error { + if _, ok := vas.accounts[account.Address.String()]; ok { + return Errorf("Account already exists: %X", account.Address) + } else { + vas.accounts[account.Address.String()] = AccountInfo{account, false} + return nil + } +} + +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 + } +} + +func (vas *VMAppState) CreateAccount(addr vm.Word) (*vm.Account, error) { + 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 { + 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 +} + +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)) + } + } + + // Update or delete storage items. + storage := merkle.NewIAVLTree( + binary.BasicCodec, // TODO change + binary.BasicCodec, // TODO change + 1024, // TODO change. + vas.state.DB, + ) + + for addrKey, value := range vas.storage { + addrKeyBytes := []byte(addrKey) + addr := addrKeyBytes[:32] + key := addrKeyBytes[32:] + 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) +} diff --git a/vm/common.go b/vm/common.go index 6ac0f6bbb..85d4966aa 100644 --- a/vm/common.go +++ b/vm/common.go @@ -4,24 +4,6 @@ import ( "encoding/binary" ) -var ( - Zero = Word{0} - One = Word{1} -) - -type Word [32]byte - -func (w Word) String() string { return string(w[:]) } -func (w Word) Copy() Word { return w } -func (w Word) Bytes() []byte { return w[:] } // copied. -func (w Word) IsZero() bool { - accum := byte(0) - for _, byt := range w { - accum |= byt - } - return accum == 0 -} - func Uint64ToWord(i uint64) Word { word := Word{} PutUint64(word[:], i) @@ -44,8 +26,6 @@ func RightPadWord(bz []byte) (word Word) { return } -//----------------------------------------------------------------------------- - func GetUint64(word Word) uint64 { return binary.LittleEndian.Uint64(word[:]) } diff --git a/vm/test/fake_app_state.go b/vm/test/fake_app_state.go index 71b124cce..16d69572e 100644 --- a/vm/test/fake_app_state.go +++ b/vm/test/fake_app_state.go @@ -15,7 +15,7 @@ type FakeAppState struct { func (fas *FakeAppState) GetAccount(addr Word) (*Account, error) { account := fas.accounts[addr.String()] - if account == nil { + if account != nil { return account, nil } else { return nil, Errorf("Invalid account addr: %v", addr) @@ -43,15 +43,15 @@ func (fas *FakeAppState) DeleteAccount(account *Account) error { } } -func (fas *FakeAppState) CreateAccount(addr Word, balance uint64) (*Account, error) { +func (fas *FakeAppState) CreateAccount(addr Word) (*Account, error) { account := fas.accounts[addr.String()] if account == nil { return &Account{ - Address: addr, - Balance: balance, - Code: nil, - Nonce: 0, - StateRoot: Zero, + Address: addr, + Balance: 0, + Code: nil, + Nonce: 0, + StorageRoot: Zero, }, nil } else { return nil, Errorf("Invalid account addr: %v", addr) @@ -83,16 +83,6 @@ func (fas *FakeAppState) SetStorage(addr Word, key Word, value Word) (bool, erro return ok, nil } -func (fas *FakeAppState) RemoveStorage(addr Word, key Word) error { - _, ok := fas.accounts[addr.String()] - if !ok { - return Errorf("Invalid account addr: %v", addr) - } - - delete(fas.storage, addr.String()+key.String()) - return nil -} - func (fas *FakeAppState) AddLog(log *Log) { fas.logs = append(fas.logs, log) } @@ -103,20 +93,20 @@ func main() { storage: make(map[string]Word), logs: nil, } - params := VMParams{ + params := Params{ BlockHeight: 0, BlockHash: Zero, BlockTime: 0, GasLimit: 0, } - ourVm := NewVM(appState, params) + ourVm := NewVM(appState, params, Zero) // Create accounts - account1, err := appState.CreateAccount(Uint64ToWord(100), 0) + account1, err := appState.CreateAccount(Uint64ToWord(100)) if err != nil { panic(err) } - account2, err := appState.CreateAccount(Uint64ToWord(101), 0) + account2, err := appState.CreateAccount(Uint64ToWord(101)) if err != nil { panic(err) } diff --git a/vm/types.go b/vm/types.go index dbe55137f..49a23b98f 100644 --- a/vm/types.go +++ b/vm/types.go @@ -6,12 +6,33 @@ const ( defaultDataStackCapacity = 10 ) +var ( + Zero = Word{0} + One = Word{1} +) + +type Word [32]byte + +func (w Word) String() string { return string(w[:]) } +func (w Word) Copy() Word { return w } +func (w Word) Bytes() []byte { return w[:] } // copied. +func (w Word) Address() []byte { return w[:20] } +func (w Word) IsZero() bool { + accum := byte(0) + for _, byt := range w { + accum |= byt + } + return accum == 0 +} + +//----------------------------------------------------------------------------- + type Account struct { - Address Word - Balance uint64 - Code []byte - Nonce uint64 - StateRoot Word + Address Word + Balance uint64 + Code []byte + Nonce uint64 + StorageRoot Word } type Log struct { @@ -27,13 +48,19 @@ type AppState interface { GetAccount(addr Word) (*Account, error) UpdateAccount(*Account) error DeleteAccount(*Account) error - CreateAccount(addr Word, balance uint64) (*Account, error) + CreateAccount(addr Word) (*Account, error) // Storage GetStorage(Word, Word) (Word, error) - SetStorage(Word, Word, Word) (bool, error) - RemoveStorage(Word, Word) error + SetStorage(Word, Word, Word) (bool, error) // Setting to Zero is deleting. // Logs AddLog(*Log) } + +type Params struct { + BlockHeight uint64 + BlockHash Word + BlockTime int64 + GasLimit uint64 +} diff --git a/vm/vm.go b/vm/vm.go index 116ad1ecb..a18f153e5 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -31,40 +31,25 @@ const ( type VM struct { appState AppState - params VMParams + params Params + origin Word callDepth int } -type VMParams struct { - BlockHeight uint64 - BlockHash Word - BlockTime int64 - GasLimit uint64 - CallStackLimit uint64 - Origin Word -} - -func NewVM(appState AppState, params VMParams) *VM { +func NewVM(appState AppState, params Params, origin Word) *VM { return &VM{ appState: appState, params: params, + origin: origin, callDepth: 0, } } -/* - // When running a transaction, the caller the pays for the fees. - - // Check caller's account balance vs feeLimit and value - if caller.Balance < (feeLimit + value) { - return nil, ErrInsufficientBalance - } - caller.Balance -= (feeLimit + value) - -*/ - -// gas: the maximum gas that will be run. +// value: The value transfered from caller to callee. +// NOTE: The value should already have been transferred to the callee. +// NOTE: When Call() fails, the value should be returned to the caller. +// gas: The maximum gas that will be run. // When the function returns, *gas will be the amount of remaining gas. func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { @@ -282,17 +267,16 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga return nil, firstErr(err, ErrInsufficientGas) } account, err_ := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported. - if err = firstErr(err, err_); err != nil { - return nil, err + if err_ != nil { + return nil, firstErr(err, err_) } balance := account.Balance stack.Push64(balance) fmt.Printf(" => %v (%X)\n", balance, addr) case ORIGIN: // 0x32 - origin := vm.params.Origin - stack.Push(origin) - fmt.Printf(" => %X\n", origin) + stack.Push(vm.origin) + fmt.Printf(" => %X\n", vm.origin) case CALLER: // 0x33 stack.Push(caller.Address) @@ -360,8 +344,8 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga return nil, firstErr(err, ErrInsufficientGas) } account, err_ := vm.appState.GetAccount(addr) - if err = firstErr(err, err_); err != nil { - return nil, err + if err_ != nil { + return nil, firstErr(err, err_) } code := account.Code l := uint64(len(code)) @@ -374,8 +358,8 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga return nil, firstErr(err, ErrInsufficientGas) } account, err_ := vm.appState.GetAccount(addr) - if err = firstErr(err, err_); err != nil { - return nil, err + if err_ != nil { + return nil, firstErr(err, err_) } code := account.Code memOff := stack.Pop64() @@ -383,11 +367,11 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga length := stack.Pop64() data, ok := subslice(code, codeOff, length) if !ok { - return nil, ErrCodeOutOfBounds + return nil, firstErr(err, ErrCodeOutOfBounds) } dest, ok := subslice(memory, memOff, length) if !ok { - return nil, ErrMemoryOutOfBounds + return nil, firstErr(err, ErrMemoryOutOfBounds) } copy(dest, data) fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) @@ -422,7 +406,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga offset := stack.Pop64() data, ok := subslice(memory, offset, 32) if !ok { - return nil, ErrMemoryOutOfBounds + return nil, firstErr(err, ErrMemoryOutOfBounds) } stack.Push(RightPadWord(data)) fmt.Printf(" => 0x%X\n", data) @@ -431,7 +415,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga offset, data := stack.Pop64(), stack.Pop() dest, ok := subslice(memory, offset, 32) if !ok { - return nil, ErrMemoryOutOfBounds + return nil, firstErr(err, ErrMemoryOutOfBounds) } copy(dest, data[:]) fmt.Printf(" => 0x%X\n", data) @@ -439,7 +423,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga case MSTORE8: // 0x53 offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF) if len(memory) <= int(offset) { - return nil, ErrMemoryOutOfBounds + return nil, firstErr(err, ErrMemoryOutOfBounds) } memory[offset] = val fmt.Printf(" => [%v] 0x%X\n", offset, val) @@ -540,8 +524,6 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga // Check balance if caller.Balance < value { return nil, firstErr(err, ErrInsufficientBalance) - } else { - caller.Balance -= value } // Create a new address @@ -551,13 +533,17 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga // TODO charge for gas to create account _ the code length * GasCreateByte - newAccount, err := vm.appState.CreateAccount(addr, value) + newAccount, err := vm.appState.CreateAccount(addr) if err != nil { stack.Push(Zero) fmt.Printf(" (*) 0x0 %v\n", err) } else { + if err_ := transfer(callee, newAccount, value); err_ != nil { + return nil, err_ // prob never happens... + } // Run the input to get the contract code. // The code as well as the input to the code are the same. + // Will it halt? Yes. ret, err_ := vm.Call(callee, newAccount, input, input, value, gas) if err_ != nil { caller.Balance += value // Return the balance @@ -592,11 +578,11 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga // Begin execution var ret []byte var err error - - // If addr is in nativeContracts if nativeContract := nativeContracts[addr]; nativeContract != nil { + // Native contract ret, err = nativeContract(args, &gasLimit) } else { + // EVM contract if ok = useGas(gas, GasGetAccount); !ok { return nil, firstErr(err, ErrInsufficientGas) } @@ -607,6 +593,9 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga if op == CALLCODE { ret, err = vm.Call(callee, callee, account.Code, args, value, gas) } else { + if err := transfer(callee, account, value); err != nil { + return nil, err + } ret, err = vm.Call(callee, account, account.Code, args, value, gas) } } @@ -642,6 +631,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga if ok = useGas(gas, GasGetAccount); !ok { return nil, firstErr(err, ErrInsufficientGas) } + // TODO if the receiver is Zero, then make it the fee. receiver, err_ := vm.appState.GetAccount(addr) if err = firstErr(err, err_); err != nil { return nil, err @@ -719,3 +709,13 @@ func createAddress(creatorAddr Word, nonce uint64) Word { PutUint64(temp[32:], nonce) return RightPadWord(sha3.Sha3(temp)[:20]) } + +func transfer(from, to *Account, amount uint64) error { + if from.Balance < amount { + return ErrInsufficientBalance + } else { + from.Balance -= amount + to.Balance += amount + return nil + } +}