Browse Source

VMAppState bridge to vm.

pull/32/head
Jae Kwon 9 years ago
parent
commit
7f12738415
9 changed files with 382 additions and 108 deletions
  1. +7
    -5
      account/account.go
  2. +1
    -1
      block/tx.go
  3. +9
    -0
      common/byteslice.go
  4. +60
    -11
      state/state.go
  5. +217
    -0
      state/vm_app_state.go
  6. +0
    -20
      vm/common.go
  7. +11
    -21
      vm/test/fake_app_state.go
  8. +35
    -8
      vm/types.go
  9. +42
    -42
      vm/vm.go

+ 7
- 5
account/account.go View File

@ -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) {


+ 1
- 1
block/tx.go View File

@ -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


+ 9
- 0
common/byteslice.go View File

@ -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


+ 60
- 11
state/state.go View File

@ -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 {


+ 217
- 0
state/vm_app_state.go View File

@ -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)
}

+ 0
- 20
vm/common.go View File

@ -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[:])
}


+ 11
- 21
vm/test/fake_app_state.go View File

@ -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)
}


+ 35
- 8
vm/types.go View File

@ -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
}

+ 42
- 42
vm/vm.go View File

@ -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
}
}

Loading…
Cancel
Save