From 79304b0dd37a84627bb73a9884398b240615ae0d Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sat, 28 Mar 2015 23:44:07 -0700 Subject: [PATCH] Create BlockCache and TxCache for performance. --- account/account.go | 10 +- binary/reflect_test.go | 36 +- blockchain/reactor.go | 2 +- common/int.go | 11 + common/word.go | 78 ++++ consensus/state.go | 2 +- mempool/mempool.go | 7 +- state/block_cache.go | 196 +++++++++++ state/common.go | 18 + state/execution.go | 593 +++++++++++++++++++++++++++++++ state/state.go | 724 +++++--------------------------------- state/state_test.go | 23 +- state/tx_cache.go | 193 ++++++++++ state/vm_app_state.go | 265 -------------- vm/common.go | 35 -- vm/gas.go | 1 - vm/native.go | 13 +- vm/stack.go | 19 +- vm/test/fake_app_state.go | 56 ++- vm/types.go | 47 +-- vm/vm.go | 123 +++---- 21 files changed, 1345 insertions(+), 1107 deletions(-) create mode 100644 common/word.go create mode 100644 state/block_cache.go create mode 100644 state/common.go create mode 100644 state/execution.go create mode 100644 state/tx_cache.go delete mode 100644 state/vm_app_state.go delete mode 100644 vm/common.go diff --git a/account/account.go b/account/account.go index 5c8bfda30..520afd70c 100644 --- a/account/account.go +++ b/account/account.go @@ -38,13 +38,13 @@ type Account struct { StorageRoot []byte // VM storage merkle root. } -func (account *Account) Copy() *Account { - accountCopy := *account - return &accountCopy +func (acc *Account) Copy() *Account { + accCopy := *acc + return &accCopy } -func (account *Account) String() string { - return fmt.Sprintf("Account{%X:%v C:%v S:%X}", account.Address, account.PubKey, len(account.Code), account.StorageRoot) +func (acc *Account) String() string { + return fmt.Sprintf("Account{%X:%v C:%v S:%X}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot) } func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) { diff --git a/binary/reflect_test.go b/binary/reflect_test.go index d8055f53a..ac5423685 100644 --- a/binary/reflect_test.go +++ b/binary/reflect_test.go @@ -2,6 +2,7 @@ package binary import ( "bytes" + "fmt" "reflect" "testing" "time" @@ -58,6 +59,35 @@ var _ = RegisterInterface( ConcreteType{&Viper{}}, ) +func TestAnimalInterface(t *testing.T) { + var foo Animal + + // Type of pointer to Animal + rt := reflect.TypeOf(&foo) + fmt.Printf("rt: %v\n", rt) + + // Type of Animal itself. + // NOTE: normally this is acquired through other means + // like introspecting on method signatures, or struct fields. + rte := rt.Elem() + fmt.Printf("rte: %v\n", rte) + + // Get a new pointer to the interface + // NOTE: calling .Interface() is to get the actual value, + // instead of reflection values. + ptr := reflect.New(rte).Interface() + fmt.Printf("ptr: %v", ptr) + + // Make a binary byteslice that represents a snake. + snakeBytes := BinaryBytes(Snake([]byte("snake"))) + snakeReader := bytes.NewReader(snakeBytes) + + // Now you can read it. + n, err := new(int64), new(error) + it := *ReadBinary(ptr, snakeReader, n, err).(*Animal) + fmt.Println(it, reflect.TypeOf(it)) +} + //------------------------------------- type Constructor func() interface{} @@ -287,9 +317,9 @@ func validateComplexArray(o interface{}, t *testing.T) { var testCases = []TestCase{} func init() { - //testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic}) - //testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex}) - //testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2}) + testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic}) + testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex}) + testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2}) testCases = append(testCases, TestCase{constructComplexArray, instantiateComplexArray, validateComplexArray}) } diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 6d65708f2..7e776ab07 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -194,7 +194,7 @@ FOR_LOOP: break SYNC_LOOP } else { bcR.pool.PopRequest() - err := bcR.state.AppendBlock(first, firstPartsHeader) + err := sm.ExecBlock(bcR.state, first, firstPartsHeader) if err != nil { // TODO This is bad, are we zombie? panic(Fmt("Failed to process committed block: %v", err)) diff --git a/common/int.go b/common/int.go index 6ca602193..f1c376d75 100644 --- a/common/int.go +++ b/common/int.go @@ -1,6 +1,7 @@ package common import ( + "encoding/binary" "sort" ) @@ -18,3 +19,13 @@ func SearchUint64s(a []uint64, x uint64) int { } func (p Uint64Slice) Search(x uint64) int { return SearchUint64s(p, x) } + +//----------------------------------------------------------------------------- + +func PutUint64(dest []byte, i uint64) { + binary.LittleEndian.PutUint64(dest, i) +} + +func GetUint64(src []byte) uint64 { + return binary.LittleEndian.Uint64(src) +} diff --git a/common/word.go b/common/word.go new file mode 100644 index 000000000..d67730e13 --- /dev/null +++ b/common/word.go @@ -0,0 +1,78 @@ +package common + +import ( + "bytes" + "encoding/binary" + "sort" +) + +var ( + Zero256 = Word256{0} + One256 = Word256{1} +) + +type Word256 [32]byte + +func (w Word256) String() string { return string(w[:]) } +func (w Word256) Copy() Word256 { return w } +func (w Word256) Bytes() []byte { return w[:] } // copied. +func (w Word256) Prefix(n int) []byte { return w[:n] } +func (w Word256) IsZero() bool { + accum := byte(0) + for _, byt := range w { + accum |= byt + } + return accum == 0 +} +func (w Word256) Compare(other Word256) int { + return bytes.Compare(w[:], other[:]) +} + +func Uint64ToWord256(i uint64) Word256 { + word := Word256{} + PutUint64(word[:], i) + return word +} + +func RightPadWord256(bz []byte) (word Word256) { + copy(word[:], bz) + return +} + +func LeftPadWord256(bz []byte) (word Word256) { + copy(word[32-len(bz):], bz) + return +} + +func Uint64FromWord256(word Word256) uint64 { + return binary.LittleEndian.Uint64(word[:]) +} + +//------------------------------------- + +type Tuple256 struct { + First Word256 + Second Word256 +} + +func (tuple Tuple256) Compare(other Tuple256) int { + firstCompare := tuple.First.Compare(other.First) + if firstCompare == 0 { + return tuple.Second.Compare(other.Second) + } else { + return firstCompare + } +} + +func Tuple256Split(t Tuple256) (Word256, Word256) { + return t.First, t.Second +} + +type Tuple256Slice []Tuple256 + +func (p Tuple256Slice) Len() int { return len(p) } +func (p Tuple256Slice) Less(i, j int) bool { + return p[i].Compare(p[j]) < 0 +} +func (p Tuple256Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p Tuple256Slice) Sort() { sort.Sort(p) } diff --git a/consensus/state.go b/consensus/state.go index 84488731f..8756df612 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1025,7 +1025,7 @@ func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartS // Commit block onto the copied state. // NOTE: Basic validation is done in state.AppendBlock(). - err := stateCopy.AppendBlock(block, blockParts.Header()) + err := sm.ExecBlock(stateCopy, block, blockParts.Header()) if err != nil { return err } else { diff --git a/mempool/mempool.go b/mempool/mempool.go index e955dfacf..28e3ab708 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -19,12 +19,14 @@ import ( type Mempool struct { mtx sync.Mutex state *sm.State + cache *sm.BlockCache txs []types.Tx } func NewMempool(state *sm.State) *Mempool { return &Mempool{ state: state, + cache: sm.NewBlockCache(state), } } @@ -36,7 +38,7 @@ func (mem *Mempool) GetState() *sm.State { func (mem *Mempool) AddTx(tx types.Tx) (err error) { mem.mtx.Lock() defer mem.mtx.Unlock() - err = mem.state.ExecTx(tx, false) + err = sm.ExecTx(mem.cache, tx, false) if err != nil { log.Debug("AddTx() error", "tx", tx, "error", err) return err @@ -62,6 +64,7 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) { mem.mtx.Lock() defer mem.mtx.Unlock() mem.state = state.Copy() + mem.cache = sm.NewBlockCache(mem.state) // First, create a lookup map of txns in new block. blockTxsMap := make(map[string]struct{}) @@ -86,7 +89,7 @@ func (mem *Mempool) ResetForBlockAndState(block *types.Block, state *sm.State) { // Next, filter all txs that aren't valid given new state. validTxs := []types.Tx{} for _, tx := range txs { - err := mem.state.ExecTx(tx, false) + err := sm.ExecTx(mem.cache, tx, false) if err == nil { log.Debug("Filter in, valid", "tx", tx) validTxs = append(validTxs, tx) diff --git a/state/block_cache.go b/state/block_cache.go new file mode 100644 index 000000000..4da8e7ce7 --- /dev/null +++ b/state/block_cache.go @@ -0,0 +1,196 @@ +package state + +import ( + "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" +) + +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]Word256 +} + +func NewBlockCache(backend *State) *BlockCache { + return &BlockCache{ + db: backend.DB, + backend: backend, + accounts: make(map[string]accountInfo), + storages: make(map[Tuple256]Word256), + } +} + +func (cache *BlockCache) State() *State { + return cache.backend +} + +//------------------------------------- +// BlockCache.account + +func (cache *BlockCache) GetAccount(addr []byte) *ac.Account { + acc, storage, removed := unpack(cache.accounts[string(addr)]) + if removed { + return nil + } else if acc != nil { + return acc + } else { + acc = cache.backend.GetAccount(addr) + storage = makeStorage(cache.db, acc.StorageRoot) + cache.accounts[string(addr)] = accountInfo{acc, storage, false} + return acc + } +} + +func (cache *BlockCache) UpdateAccount(acc *ac.Account) { + addr := acc.Address + // SANITY CHECK + _, storage, removed := unpack(cache.accounts[string(addr)]) + if removed { + panic("UpdateAccount on a removed account") + } + // SANITY CHECK END + cache.accounts[string(addr)] = accountInfo{acc, storage, false} +} + +func (cache *BlockCache) RemoveAccount(addr []byte) { + // SANITY CHECK + _, _, removed := unpack(cache.accounts[string(addr)]) + if removed { + panic("RemoveAccount on a removed account") + } + // SANITY CHECK END + cache.accounts[string(addr)] = accountInfo{nil, nil, true} +} + +// BlockCache.account +//------------------------------------- +// BlockCache.storage + +func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) { + // Check cache + value, ok := cache.storages[Tuple256{addr, key}] + if ok { + return value + } + + // Get or load storage + _, storage, removed := unpack(cache.accounts[string(addr.Prefix(20))]) + if removed { + panic("GetStorage() on removed account") + } + + // Load and set cache + _, val_ := storage.Get(key.Bytes()) + value = Zero256 + if val_ != nil { + value = RightPadWord256(val_.([]byte)) + } + cache.storages[Tuple256{addr, key}] = value + return value +} + +// NOTE: Set value to zero to removed from the trie. +func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) { + _, _, removed := unpack(cache.accounts[string(addr.Prefix(20))]) + if removed { + panic("SetStorage() on a removed account") + } + cache.storages[Tuple256{addr, key}] = value +} + +// BlockCache.storage +//------------------------------------- + +// 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 := unpack(cache.accounts[string(addr.Prefix(20))]) + curAddr = addr + curAcc = acc + curAccRemoved = removed + curStorage = storage + } + if curAccRemoved { + continue + } + value := cache.storages[storageKey] + if value.IsZero() { + curStorage.Remove(key.Bytes()) + } else { + curStorage.Set(key.Bytes(), value.Bytes()) + } + } + + // 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 := unpack(cache.accounts[addrStr]) + 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 { + panic(Fmt("Account should not be nil for addr: %X", acc.Address)) + } + acc.StorageRoot = storage.Save() + cache.backend.UpdateAccount(acc) + } + } + +} + +//----------------------------------------------------------------------------- + +type accountInfo struct { + account *ac.Account + storage merkle.Tree + removed bool +} + +func unpack(accInfo accountInfo) (*ac.Account, merkle.Tree, bool) { + return accInfo.account, accInfo.storage, accInfo.removed +} diff --git a/state/common.go b/state/common.go new file mode 100644 index 000000000..342d35779 --- /dev/null +++ b/state/common.go @@ -0,0 +1,18 @@ +package state + +import ( + ac "github.com/tendermint/tendermint/account" + . "github.com/tendermint/tendermint/common" + "github.com/tendermint/tendermint/vm" +) + +type AccountGetter interface { + GetAccount(addr []byte) *ac.Account +} + +type VMAccountState interface { + GetAccount(addr Word256) *vm.Account + UpdateAccount(acc *vm.Account) + RemoveAccount(acc *vm.Account) + CreateAccount(creator *vm.Account) *vm.Account +} diff --git a/state/execution.go b/state/execution.go new file mode 100644 index 000000000..d7b3b78f7 --- /dev/null +++ b/state/execution.go @@ -0,0 +1,593 @@ +package state + +import ( + "bytes" + "errors" + + "github.com/tendermint/tendermint/account" + . "github.com/tendermint/tendermint/common" + "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/vm" +) + +// NOTE: If an error occurs during block execution, state will be left +// at an invalid state. Copy the state before calling ExecBlock! +func ExecBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error { + err := execBlock(s, block, blockPartsHeader) + if err != nil { + return err + } + // State.Hash should match block.StateHash + stateHash := s.Hash() + if !bytes.Equal(stateHash, block.StateHash) { + return Errorf("Invalid state hash. Expected %X, got %X", + stateHash, block.StateHash) + } + return nil +} + +// executes transactions of a block, does not check block.StateHash +// NOTE: If an error occurs during block execution, state will be left +// at an invalid state. Copy the state before calling execBlock! +func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error { + // Basic block validation. + err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime) + if err != nil { + return err + } + + // Validate block Validation. + if block.Height == 1 { + if len(block.Validation.Commits) != 0 { + return errors.New("Block at height 1 (first block) should have no Validation commits") + } + } else { + if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() { + return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", + s.LastBondedValidators.Size(), len(block.Validation.Commits))) + } + var sumVotingPower uint64 + s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool { + commit := block.Validation.Commits[index] + if commit.IsZero() { + return false + } else { + vote := &types.Vote{ + Height: block.Height - 1, + Round: commit.Round, + Type: types.VoteTypeCommit, + BlockHash: block.LastBlockHash, + BlockParts: block.LastBlockParts, + } + if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) { + sumVotingPower += val.VotingPower + return false + } else { + log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote)) + err = errors.New("Invalid validation signature") + return true + } + } + }) + if err != nil { + return err + } + if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 { + return errors.New("Insufficient validation voting power") + } + } + + // Update Validator.LastCommitHeight as necessary. + for i, commit := range block.Validation.Commits { + if commit.IsZero() { + continue + } + _, val := s.LastBondedValidators.GetByIndex(uint(i)) + if val == nil { + panic(Fmt("Failed to fetch validator at index %v", i)) + } + if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil { + val_.LastCommitHeight = block.Height - 1 + updated := s.BondedValidators.Update(val_) + if !updated { + panic("Failed to update bonded validator LastCommitHeight") + } + } else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil { + val_.LastCommitHeight = block.Height - 1 + updated := s.UnbondingValidators.Update(val_) + if !updated { + panic("Failed to update unbonding validator LastCommitHeight") + } + } else { + panic("Could not find validator") + } + } + + // Remember LastBondedValidators + s.LastBondedValidators = s.BondedValidators.Copy() + + // Create BlockCache to cache changes to state. + blockCache := NewBlockCache(s) + + // Commit each tx + for _, tx := range block.Data.Txs { + err := ExecTx(blockCache, tx, true) + if err != nil { + return InvalidTxError{tx, err} + } + } + + // Now sync the BlockCache to the backend. + blockCache.Sync() + + // If any unbonding periods are over, + // reward account with bonded coins. + toRelease := []*Validator{} + s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool { + if val.UnbondHeight+unbondingPeriodBlocks < block.Height { + toRelease = append(toRelease, val) + } + return false + }) + for _, val := range toRelease { + s.releaseValidator(val) + } + + // If any validators haven't signed in a while, + // unbond them, they have timed out. + toTimeout := []*Validator{} + s.BondedValidators.Iterate(func(index uint, val *Validator) bool { + lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight) + if lastActivityHeight+validatorTimeoutBlocks < block.Height { + log.Info("Validator timeout", "validator", val, "height", block.Height) + toTimeout = append(toTimeout, val) + } + return false + }) + for _, val := range toTimeout { + s.unbondValidator(val) + } + + // Increment validator AccumPowers + s.BondedValidators.IncrementAccum(1) + + s.LastBlockHeight = block.Height + s.LastBlockHash = block.Hash() + s.LastBlockParts = blockPartsHeader + s.LastBlockTime = block.Time + return nil +} + +// The accounts from the TxInputs must either already have +// account.PubKey.(type) != PubKeyNil, (it must be known), +// or it must be specified in the TxInput. If redeclared, +// the TxInput is modified and input.PubKey set to PubKeyNil. +func getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) { + accounts := map[string]*account.Account{} + for _, in := range ins { + // Account shouldn't be duplicated + if _, ok := accounts[string(in.Address)]; ok { + return nil, types.ErrTxDuplicateAddress + } + acc := state.GetAccount(in.Address) + if acc == nil { + return nil, types.ErrTxInvalidAddress + } + // PubKey should be present in either "account" or "in" + if err := checkInputPubKey(acc, in); err != nil { + return nil, err + } + accounts[string(in.Address)] = acc + } + for _, out := range outs { + // Account shouldn't be duplicated + if _, ok := accounts[string(out.Address)]; ok { + return nil, types.ErrTxDuplicateAddress + } + acc := state.GetAccount(out.Address) + // output account may be nil (new) + if acc == nil { + acc = &account.Account{ + Address: out.Address, + PubKey: account.PubKeyNil{}, + Sequence: 0, + Balance: 0, + } + } + accounts[string(out.Address)] = acc + } + return accounts, nil +} + +func checkInputPubKey(acc *account.Account, in *types.TxInput) error { + if _, isNil := acc.PubKey.(account.PubKeyNil); isNil { + if _, isNil := in.PubKey.(account.PubKeyNil); isNil { + return types.ErrTxUnknownPubKey + } + if !bytes.Equal(in.PubKey.Address(), acc.Address) { + return types.ErrTxInvalidPubKey + } + acc.PubKey = in.PubKey + } else { + in.PubKey = account.PubKeyNil{} + } + return nil +} + +func validateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total uint64, err error) { + for _, in := range ins { + acc := accounts[string(in.Address)] + if acc == nil { + panic("validateInputs() expects account in accounts") + } + err = validateInput(acc, signBytes, in) + if err != nil { + return + } + // Good. Add amount to total + total += in.Amount + } + return total, nil +} + +func validateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (err error) { + // Check TxInput basic + if err := in.ValidateBasic(); err != nil { + return err + } + // Check signatures + if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { + return types.ErrTxInvalidSignature + } + // Check sequences + if acc.Sequence+1 != in.Sequence { + return types.ErrTxInvalidSequence{ + Got: uint64(in.Sequence), + Expected: uint64(acc.Sequence + 1), + } + } + // Check amount + if acc.Balance < in.Amount { + return types.ErrTxInsufficientFunds + } + return nil +} + +func validateOutputs(outs []*types.TxOutput) (total uint64, err error) { + for _, out := range outs { + // Check TxOutput basic + if err := out.ValidateBasic(); err != nil { + return 0, err + } + // Good. Add amount to total + total += out.Amount + } + return total, nil +} + +func adjustByInputs(accounts map[string]*account.Account, ins []*types.TxInput) { + for _, in := range ins { + acc := accounts[string(in.Address)] + if acc == nil { + panic("adjustByInputs() expects account in accounts") + } + if acc.Balance < in.Amount { + panic("adjustByInputs() expects sufficient funds") + } + acc.Balance -= in.Amount + acc.Sequence += 1 + } +} + +func adjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutput) { + for _, out := range outs { + acc := accounts[string(out.Address)] + if acc == nil { + panic("adjustByOutputs() expects account in accounts") + } + acc.Balance += out.Amount + } +} + +// If the tx is invalid, an error will be returned. +// Unlike ExecBlock(), state will not be altered. +func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error { + + // TODO: do something with fees + fees := uint64(0) + _s := blockCache.State() // hack to access validators. + + // Exec tx + switch tx := tx_.(type) { + case *types.SendTx: + accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, tx.Outputs) + if err != nil { + return err + } + signBytes := account.SignBytes(tx) + inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) + if err != nil { + return err + } + outTotal, err := validateOutputs(tx.Outputs) + if err != nil { + return err + } + if outTotal > inTotal { + return types.ErrTxInsufficientFunds + } + fee := inTotal - outTotal + fees += fee + + // Good! Adjust accounts + adjustByInputs(accounts, tx.Inputs) + adjustByOutputs(accounts, tx.Outputs) + for _, acc := range accounts { + blockCache.UpdateAccount(acc) + } + return nil + + case *types.CallTx: + var inAcc, outAcc *account.Account + + // Validate input + inAcc = blockCache.GetAccount(tx.Input.Address) + if inAcc == nil { + log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) + return types.ErrTxInvalidAddress + } + // pubKey should be present in either "inAcc" or "tx.Input" + if err := checkInputPubKey(inAcc, tx.Input); err != nil { + log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) + return err + } + signBytes := account.SignBytes(tx) + err := validateInput(inAcc, signBytes, tx.Input) + if err != nil { + log.Debug(Fmt("validateInput failed on %X:", tx.Input.Address)) + return err + } + if tx.Input.Amount < tx.Fee { + log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) + return types.ErrTxInsufficientFunds + } + + createAccount := len(tx.Address) == 0 + if !createAccount { + // Validate output + if len(tx.Address) != 20 { + log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address)) + return types.ErrTxInvalidAddress + } + // this may be nil if we are still in mempool and contract was created in same block as this tx + // but that's fine, because the account will be created properly when the create tx runs in the block + // and then this won't return nil. otherwise, we take their fee + outAcc = blockCache.GetAccount(tx.Address) + } + + log.Debug(Fmt("Out account: %v", outAcc)) + + // Good! + value := tx.Input.Amount - tx.Fee + inAcc.Sequence += 1 + + if runCall { + + var ( + gas uint64 = tx.GasLimit + err error = nil + caller *vm.Account = toVMAccount(inAcc) + callee *vm.Account = nil + code []byte = nil + txCache = NewTxCache(blockCache) + params = vm.Params{ + BlockHeight: uint64(_s.LastBlockHeight), + BlockHash: RightPadWord256(_s.LastBlockHash), + BlockTime: _s.LastBlockTime.Unix(), + GasLimit: 10000000, + } + ) + + // Maybe create a new callee account if + // this transaction is creating a new contract. + if !createAccount { + if outAcc == nil { + // take fees (sorry pal) + inAcc.Balance -= tx.Fee + blockCache.UpdateAccount(inAcc) + log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address)) + return types.ErrTxInvalidAddress + + } + callee = toVMAccount(outAcc) + code = callee.Code + log.Debug(Fmt("Calling contract %X with code %X", callee.Address, callee.Code)) + } else { + callee = txCache.CreateAccount(caller) + log.Debug(Fmt("Created new account %X", callee.Address)) + code = tx.Data + } + log.Debug(Fmt("Code for this contract: %X", code)) + + txCache.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe. + txCache.UpdateAccount(callee) // because we adjusted by input above. + vmach := vm.NewVM(txCache, params, caller.Address) + // NOTE: Call() transfers the value from caller to callee iff call succeeds. + ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas) + if err != nil { + // Failure. Charge the gas fee. The 'value' was otherwise not transferred. + log.Debug(Fmt("Error on execution: %v", err)) + inAcc.Balance -= tx.Fee + blockCache.UpdateAccount(inAcc) + // Throw away 'txCache' which holds incomplete updates (don't sync it). + } else { + log.Debug("Successful execution") + // Success + if createAccount { + callee.Code = ret + } + + txCache.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. + inAcc.Balance -= value + if createAccount { + inAcc.Sequence += 1 + } + blockCache.UpdateAccount(inAcc) + } + + return nil + + case *types.BondTx: + valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address()) + if valInfo != nil { + // TODO: In the future, check that the validator wasn't destroyed, + // add funds, merge UnbondTo outputs, and unbond validator. + return errors.New("Adding coins to existing validators not yet supported") + } + accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, nil) + if err != nil { + return err + } + signBytes := account.SignBytes(tx) + inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) + if err != nil { + return err + } + if err := tx.PubKey.ValidateBasic(); err != nil { + return err + } + outTotal, err := validateOutputs(tx.UnbondTo) + if err != nil { + return err + } + if outTotal > inTotal { + return types.ErrTxInsufficientFunds + } + fee := inTotal - outTotal + fees += fee + + // Good! Adjust accounts + adjustByInputs(accounts, tx.Inputs) + for _, acc := range accounts { + blockCache.UpdateAccount(acc) + } + // Add ValidatorInfo + _s.SetValidatorInfo(&ValidatorInfo{ + Address: tx.PubKey.Address(), + PubKey: tx.PubKey, + UnbondTo: tx.UnbondTo, + FirstBondHeight: _s.LastBlockHeight + 1, + FirstBondAmount: outTotal, + }) + // Add Validator + added := _s.BondedValidators.Add(&Validator{ + Address: tx.PubKey.Address(), + PubKey: tx.PubKey, + BondHeight: _s.LastBlockHeight + 1, + VotingPower: outTotal, + Accum: 0, + }) + if !added { + panic("Failed to add validator") + } + return nil + + case *types.UnbondTx: + // The validator must be active + _, val := _s.BondedValidators.GetByAddress(tx.Address) + if val == nil { + return types.ErrTxInvalidAddress + } + + // Verify the signature + signBytes := account.SignBytes(tx) + if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { + return types.ErrTxInvalidSignature + } + + // tx.Height must be greater than val.LastCommitHeight + if tx.Height <= val.LastCommitHeight { + return errors.New("Invalid unbond height") + } + + // Good! + _s.unbondValidator(val) + return nil + + case *types.RebondTx: + // The validator must be inactive + _, val := _s.UnbondingValidators.GetByAddress(tx.Address) + if val == nil { + return types.ErrTxInvalidAddress + } + + // Verify the signature + signBytes := account.SignBytes(tx) + if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { + return types.ErrTxInvalidSignature + } + + // tx.Height must be equal to the next height + if tx.Height != _s.LastBlockHeight+1 { + return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", _s.LastBlockHeight+1, tx.Height)) + } + + // Good! + _s.rebondValidator(val) + return nil + + case *types.DupeoutTx: + // Verify the signatures + _, accused := _s.BondedValidators.GetByAddress(tx.Address) + if accused == nil { + _, accused = _s.UnbondingValidators.GetByAddress(tx.Address) + if accused == nil { + return types.ErrTxInvalidAddress + } + } + voteASignBytes := account.SignBytes(&tx.VoteA) + voteBSignBytes := account.SignBytes(&tx.VoteB) + if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) || + !accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) { + return types.ErrTxInvalidSignature + } + + // Verify equivocation + // TODO: in the future, just require one vote from a previous height that + // doesn't exist on this chain. + if tx.VoteA.Height != tx.VoteB.Height { + return errors.New("DupeoutTx heights don't match") + } + if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round { + // Check special case (not an error, validator must be slashed!) + // Validators should not sign another vote after committing. + } else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round { + // We need to check both orderings of the votes + } else { + if tx.VoteA.Round != tx.VoteB.Round { + return errors.New("DupeoutTx rounds don't match") + } + if tx.VoteA.Type != tx.VoteB.Type { + return errors.New("DupeoutTx types don't match") + } + if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { + return errors.New("DupeoutTx blockhashes shouldn't match") + } + } + + // Good! (Bad validator!) + _s.destroyValidator(accused) + return nil + + default: + panic("Unknown Tx type") + } +} diff --git a/state/state.go b/state/state.go index 0fcd64f7b..700202b87 100644 --- a/state/state.go +++ b/state/state.go @@ -2,17 +2,14 @@ package state import ( "bytes" - "errors" "fmt" "time" "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" - "github.com/tendermint/tendermint/vm" ) var ( @@ -25,17 +22,6 @@ var ( //----------------------------------------------------------------------------- -type InvalidTxError struct { - Tx types.Tx - Reason error -} - -func (txErr InvalidTxError) Error() string { - return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason) -} - -//----------------------------------------------------------------------------- - // NOTE: not goroutine-safe. type State struct { DB dbm.DB @@ -78,7 +64,6 @@ func LoadState(db dbm.DB) *State { return s } -// Save this state into the db. func (s *State) Save() { s.accounts.Save() s.validatorInfos.Save() @@ -98,6 +83,9 @@ func (s *State) Save() { s.DB.Set(stateKey, buf.Bytes()) } +// CONTRACT: +// Copy() is a cheap way to take a snapshot, +// as if State were copied by value. func (s *State) Copy() *State { return &State{ DB: s.DB, @@ -113,437 +101,81 @@ func (s *State) Copy() *State { } } -// The accounts from the TxInputs must either already have -// account.PubKey.(type) != PubKeyNil, (it must be known), -// or it must be specified in the TxInput. If redeclared, -// the TxInput is modified and input.PubKey set to PubKeyNil. -func (s *State) GetOrMakeAccounts(ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) { - accounts := map[string]*account.Account{} - for _, in := range ins { - // Account shouldn't be duplicated - if _, ok := accounts[string(in.Address)]; ok { - return nil, types.ErrTxDuplicateAddress - } - acc := s.GetAccount(in.Address) - if acc == nil { - return nil, types.ErrTxInvalidAddress - } - // PubKey should be present in either "account" or "in" - if err := checkInputPubKey(acc, in); err != nil { - return nil, err - } - accounts[string(in.Address)] = acc +// Returns a hash that represents the state data, excluding Last* +func (s *State) Hash() []byte { + hashables := []merkle.Hashable{ + s.BondedValidators, + s.UnbondingValidators, + s.accounts, + s.validatorInfos, } - for _, out := range outs { - // Account shouldn't be duplicated - if _, ok := accounts[string(out.Address)]; ok { - return nil, types.ErrTxDuplicateAddress - } - acc := s.GetAccount(out.Address) - // output account may be nil (new) - if acc == nil { - acc = &account.Account{ - Address: out.Address, - PubKey: account.PubKeyNil{}, - Sequence: 0, - Balance: 0, - } - } - accounts[string(out.Address)] = acc - } - return accounts, nil + return merkle.HashFromHashables(hashables) } -func checkInputPubKey(acc *account.Account, in *types.TxInput) error { - if _, isNil := acc.PubKey.(account.PubKeyNil); isNil { - if _, isNil := in.PubKey.(account.PubKeyNil); isNil { - return types.ErrTxUnknownPubKey - } - if !bytes.Equal(in.PubKey.Address(), acc.Address) { - return types.ErrTxInvalidPubKey - } - acc.PubKey = in.PubKey - } else { - in.PubKey = account.PubKeyNil{} - } - return nil -} - -func (s *State) ValidateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total uint64, err error) { - for _, in := range ins { - acc := accounts[string(in.Address)] - if acc == nil { - panic("ValidateInputs() expects account in accounts") - } - err = s.ValidateInput(acc, signBytes, in) - if err != nil { - return - } - // Good. Add amount to total - total += in.Amount - } - return total, nil -} - -func (s *State) ValidateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (err error) { - // Check TxInput basic - if err := in.ValidateBasic(); err != nil { +// Mutates the block in place and updates it with new state hash. +func (s *State) SetBlockStateHash(block *types.Block) error { + sCopy := s.Copy() + err := execBlock(sCopy, block, types.PartSetHeader{}) + if err != nil { return err } - // Check signatures - if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { - return types.ErrTxInvalidSignature - } - // Check sequences - if acc.Sequence+1 != in.Sequence { - return types.ErrTxInvalidSequence{ - Got: uint64(in.Sequence), - Expected: uint64(acc.Sequence + 1), - } - } - // Check amount - if acc.Balance < in.Amount { - return types.ErrTxInsufficientFunds - } + // Set block.StateHash + block.StateHash = sCopy.Hash() return nil } -func (s *State) ValidateOutputs(outs []*types.TxOutput) (total uint64, err error) { - for _, out := range outs { - // Check TxOutput basic - if err := out.ValidateBasic(); err != nil { - return 0, err - } - // Good. Add amount to total - total += out.Amount +//------------------------------------- +// State.accounts + +// The returned Account is a copy, so mutating it +// has no side effects. +// Implements Statelike +func (s *State) GetAccount(address []byte) *account.Account { + _, acc := s.accounts.Get(address) + if acc == nil { + return nil } - return total, nil + return acc.(*account.Account).Copy() } -func (s *State) AdjustByInputs(accounts map[string]*account.Account, ins []*types.TxInput) { - for _, in := range ins { - acc := accounts[string(in.Address)] - if acc == nil { - panic("AdjustByInputs() expects account in accounts") - } - if acc.Balance < in.Amount { - panic("AdjustByInputs() expects sufficient funds") - } - acc.Balance -= in.Amount - acc.Sequence += 1 - } +// The account is copied before setting, so mutating it +// afterwards has no side effects. +// Implements Statelike +func (s *State) UpdateAccount(account *account.Account) bool { + return s.accounts.Set(account.Address, account.Copy()) } -func (s *State) AdjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutput) { - for _, out := range outs { - acc := accounts[string(out.Address)] - if acc == nil { - panic("AdjustByOutputs() expects account in accounts") - } - acc.Balance += out.Amount - } +// Implements Statelike +func (s *State) RemoveAccount(address []byte) bool { + _, removed := s.accounts.Remove(address) + return removed } -// If the tx is invalid, an error will be returned. -// Unlike AppendBlock(), state will not be altered. -func (s *State) ExecTx(tx_ types.Tx, runCall bool) error { +// The returned Account is a copy, so mutating it +// has no side effects. +func (s *State) GetAccounts() merkle.Tree { + return s.accounts.Copy() +} - // TODO: do something with fees - fees := uint64(0) +// State.accounts +//------------------------------------- +// State.validators - // Exec tx - switch tx := tx_.(type) { - case *types.SendTx: - accounts, err := s.GetOrMakeAccounts(tx.Inputs, tx.Outputs) - if err != nil { - return err - } - signBytes := account.SignBytes(tx) - inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs) - if err != nil { - return err - } - outTotal, err := s.ValidateOutputs(tx.Outputs) - if err != nil { - return err - } - if outTotal > inTotal { - return types.ErrTxInsufficientFunds - } - fee := inTotal - outTotal - fees += fee - - // Good! Adjust accounts - s.AdjustByInputs(accounts, tx.Inputs) - s.AdjustByOutputs(accounts, tx.Outputs) - s.UpdateAccounts(accounts) +// The returned ValidatorInfo is a copy, so mutating it +// has no side effects. +func (s *State) GetValidatorInfo(address []byte) *ValidatorInfo { + _, valInfo := s.validatorInfos.Get(address) + if valInfo == nil { return nil - - case *types.CallTx: - var inAcc, outAcc *account.Account - - // Validate input - inAcc = s.GetAccount(tx.Input.Address) - if inAcc == nil { - log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) - return types.ErrTxInvalidAddress - } - // pubKey should be present in either "inAcc" or "tx.Input" - if err := checkInputPubKey(inAcc, tx.Input); err != nil { - log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) - return err - } - signBytes := account.SignBytes(tx) - err := s.ValidateInput(inAcc, signBytes, tx.Input) - if err != nil { - log.Debug(Fmt("ValidateInput failed on %X:", tx.Input.Address)) - return err - } - if tx.Input.Amount < tx.Fee { - log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) - return types.ErrTxInsufficientFunds - } - - createAccount := len(tx.Address) == 0 - if !createAccount { - // Validate output - if len(tx.Address) != 20 { - log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address)) - return types.ErrTxInvalidAddress - } - // this may be nil if we are still in mempool and contract was created in same block as this tx - // but that's fine, because the account will be created properly when the create tx runs in the block - // and then this won't return nil. otherwise, we take their fee - outAcc = s.GetAccount(tx.Address) - } - - log.Debug(Fmt("Out account: %v", outAcc)) - - // Good! - value := tx.Input.Amount - tx.Fee - inAcc.Sequence += 1 - - if runCall { - - var ( - gas uint64 = tx.GasLimit - err error = nil - caller *vm.Account = toVMAccount(inAcc) - callee *vm.Account = nil - code []byte = nil - appState = NewVMAppState(s) // TODO: confusing. - params = vm.Params{ - BlockHeight: uint64(s.LastBlockHeight), - BlockHash: vm.BytesToWord(s.LastBlockHash), - BlockTime: s.LastBlockTime.Unix(), - GasLimit: 10000000, - } - ) - - // Maybe create a new callee account if - // this transaction is creating a new contract. - if !createAccount { - if outAcc == nil { - // take fees (sorry pal) - inAcc.Balance -= tx.Fee - s.UpdateAccount(inAcc) - log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address)) - return types.ErrTxInvalidAddress - - } - callee = toVMAccount(outAcc) - code = callee.Code - log.Debug(Fmt("Calling contract %X with code %X", callee.Address.Address(), callee.Code)) - } else { - callee, err = appState.CreateAccount(caller) - if err != nil { - log.Debug(Fmt("Error creating account")) - return err - } - log.Debug(Fmt("Created new account %X", callee.Address.Address())) - code = tx.Data - } - log.Debug(Fmt("Code for this contract: %X", code)) - - appState.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe. - appState.UpdateAccount(callee) // because we adjusted by input above. - vmach := vm.NewVM(appState, params, caller.Address) - // NOTE: Call() transfers the value from caller to callee iff call succeeds. - ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas) - if err != nil { - // Failure. Charge the gas fee. The 'value' was otherwise not transferred. - log.Debug(Fmt("Error on execution: %v", err)) - inAcc.Balance -= tx.Fee - s.UpdateAccount(inAcc) - // Throw away 'appState' which holds incomplete updates (don't sync it). - } else { - log.Debug("Successful execution") - // Success - if createAccount { - callee.Code = ret - } - - 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. - inAcc.Balance -= value - if createAccount { - inAcc.Sequence += 1 - } - s.UpdateAccount(inAcc) - } - - return nil - - case *types.BondTx: - valInfo := s.GetValidatorInfo(tx.PubKey.Address()) - if valInfo != nil { - // TODO: In the future, check that the validator wasn't destroyed, - // add funds, merge UnbondTo outputs, and unbond validator. - return errors.New("Adding coins to existing validators not yet supported") - } - accounts, err := s.GetOrMakeAccounts(tx.Inputs, nil) - if err != nil { - return err - } - signBytes := account.SignBytes(tx) - inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs) - if err != nil { - return err - } - if err := tx.PubKey.ValidateBasic(); err != nil { - return err - } - outTotal, err := s.ValidateOutputs(tx.UnbondTo) - if err != nil { - return err - } - if outTotal > inTotal { - return types.ErrTxInsufficientFunds - } - fee := inTotal - outTotal - fees += fee - - // Good! Adjust accounts - s.AdjustByInputs(accounts, tx.Inputs) - s.UpdateAccounts(accounts) - // Add ValidatorInfo - s.SetValidatorInfo(&ValidatorInfo{ - Address: tx.PubKey.Address(), - PubKey: tx.PubKey, - UnbondTo: tx.UnbondTo, - FirstBondHeight: s.LastBlockHeight + 1, - FirstBondAmount: outTotal, - }) - // Add Validator - added := s.BondedValidators.Add(&Validator{ - Address: tx.PubKey.Address(), - PubKey: tx.PubKey, - BondHeight: s.LastBlockHeight + 1, - VotingPower: outTotal, - Accum: 0, - }) - if !added { - panic("Failed to add validator") - } - return nil - - case *types.UnbondTx: - // The validator must be active - _, val := s.BondedValidators.GetByAddress(tx.Address) - if val == nil { - return types.ErrTxInvalidAddress - } - - // Verify the signature - signBytes := account.SignBytes(tx) - if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { - return types.ErrTxInvalidSignature - } - - // tx.Height must be greater than val.LastCommitHeight - if tx.Height <= val.LastCommitHeight { - return errors.New("Invalid unbond height") - } - - // Good! - s.unbondValidator(val) - return nil - - case *types.RebondTx: - // The validator must be inactive - _, val := s.UnbondingValidators.GetByAddress(tx.Address) - if val == nil { - return types.ErrTxInvalidAddress - } - - // Verify the signature - signBytes := account.SignBytes(tx) - if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { - return types.ErrTxInvalidSignature - } - - // tx.Height must be equal to the next height - if tx.Height != s.LastBlockHeight+1 { - return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", s.LastBlockHeight+1, tx.Height)) - } - - // Good! - s.rebondValidator(val) - return nil - - case *types.DupeoutTx: - // Verify the signatures - _, accused := s.BondedValidators.GetByAddress(tx.Address) - if accused == nil { - _, accused = s.UnbondingValidators.GetByAddress(tx.Address) - if accused == nil { - return types.ErrTxInvalidAddress - } - } - voteASignBytes := account.SignBytes(&tx.VoteA) - voteBSignBytes := account.SignBytes(&tx.VoteB) - if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) || - !accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) { - return types.ErrTxInvalidSignature - } - - // Verify equivocation - // TODO: in the future, just require one vote from a previous height that - // doesn't exist on this chain. - if tx.VoteA.Height != tx.VoteB.Height { - return errors.New("DupeoutTx heights don't match") - } - if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round { - // Check special case (not an error, validator must be slashed!) - // Validators should not sign another vote after committing. - } else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round { - // We need to check both orderings of the votes - } else { - if tx.VoteA.Round != tx.VoteB.Round { - return errors.New("DupeoutTx rounds don't match") - } - if tx.VoteA.Type != tx.VoteB.Type { - return errors.New("DupeoutTx types don't match") - } - if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { - return errors.New("DupeoutTx blockhashes shouldn't match") - } - } - - // Good! (Bad validator!) - s.destroyValidator(accused) - return nil - - default: - panic("Unknown Tx type") } + return valInfo.(*ValidatorInfo).Copy() +} + +// Returns false if new, true if updated. +// The valInfo is copied before setting, so mutating it +// afterwards has no side effects. +func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) { + return s.validatorInfos.Set(valInfo.Address, valInfo.Copy()) } func (s *State) unbondValidator(val *Validator) { @@ -582,12 +214,14 @@ func (s *State) releaseValidator(val *Validator) { s.SetValidatorInfo(valInfo) // Send coins back to UnbondTo outputs - accounts, err := s.GetOrMakeAccounts(nil, valInfo.UnbondTo) + accounts, err := getOrMakeAccounts(s, nil, valInfo.UnbondTo) if err != nil { panic("Couldn't get or make unbondTo accounts") } - s.AdjustByOutputs(accounts, valInfo.UnbondTo) - s.UpdateAccounts(accounts) + adjustByOutputs(accounts, valInfo.UnbondTo) + for _, acc := range accounts { + s.UpdateAccount(acc) + } // Remove validator from UnbondingValidators _, removed := s.UnbondingValidators.Remove(val.Address) @@ -617,220 +251,26 @@ func (s *State) destroyValidator(val *Validator) { } -// NOTE: If an error occurs during block execution, state will be left -// at an invalid state. Copy the state before calling AppendBlock! -func (s *State) AppendBlock(block *types.Block, blockPartsHeader types.PartSetHeader) error { - err := s.appendBlock(block, blockPartsHeader) - if err != nil { - return err - } - // State.Hash should match block.StateHash - stateHash := s.Hash() - if !bytes.Equal(stateHash, block.StateHash) { - return Errorf("Invalid state hash. Expected %X, got %X", - stateHash, block.StateHash) - } - return nil +// State.validators +//------------------------------------- +// State.storage + +func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) { + storage = merkle.NewIAVLTree(binary.BasicCodec, binary.BasicCodec, 1024, s.DB) + storage.Load(hash) + return storage } -// Mutates the block in place and updates it with new state hash. -func (s *State) SetBlockStateHash(block *types.Block) error { - sCopy := s.Copy() - err := sCopy.appendBlock(block, types.PartSetHeader{}) - if err != nil { - return err - } - // Set block.StateHash - block.StateHash = sCopy.Hash() - return nil +// State.storage +//------------------------------------- + +//----------------------------------------------------------------------------- + +type InvalidTxError struct { + Tx types.Tx + Reason error } -// Appends the block, does not check block.StateHash -// NOTE: If an error occurs during block execution, state will be left -// at an invalid state. Copy the state before calling appendBlock! -func (s *State) appendBlock(block *types.Block, blockPartsHeader types.PartSetHeader) error { - // Basic block validation. - err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime) - if err != nil { - return err - } - - // Validate block Validation. - if block.Height == 1 { - if len(block.Validation.Commits) != 0 { - return errors.New("Block at height 1 (first block) should have no Validation commits") - } - } else { - if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() { - return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", - s.LastBondedValidators.Size(), len(block.Validation.Commits))) - } - var sumVotingPower uint64 - s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool { - commit := block.Validation.Commits[index] - if commit.IsZero() { - return false - } else { - vote := &types.Vote{ - Height: block.Height - 1, - Round: commit.Round, - Type: types.VoteTypeCommit, - BlockHash: block.LastBlockHash, - BlockParts: block.LastBlockParts, - } - if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) { - sumVotingPower += val.VotingPower - return false - } else { - log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote)) - err = errors.New("Invalid validation signature") - return true - } - } - }) - if err != nil { - return err - } - if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 { - return errors.New("Insufficient validation voting power") - } - } - - // Update Validator.LastCommitHeight as necessary. - for i, commit := range block.Validation.Commits { - if commit.IsZero() { - continue - } - _, val := s.LastBondedValidators.GetByIndex(uint(i)) - if val == nil { - panic(Fmt("Failed to fetch validator at index %v", i)) - } - if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil { - val_.LastCommitHeight = block.Height - 1 - updated := s.BondedValidators.Update(val_) - if !updated { - panic("Failed to update bonded validator LastCommitHeight") - } - } else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil { - val_.LastCommitHeight = block.Height - 1 - updated := s.UnbondingValidators.Update(val_) - if !updated { - panic("Failed to update unbonding validator LastCommitHeight") - } - } else { - panic("Could not find validator") - } - } - - // Remember LastBondedValidators - s.LastBondedValidators = s.BondedValidators.Copy() - - // Commit each tx - for _, tx := range block.Data.Txs { - err := s.ExecTx(tx, true) - if err != nil { - return InvalidTxError{tx, err} - } - } - - // If any unbonding periods are over, - // reward account with bonded coins. - toRelease := []*Validator{} - s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool { - if val.UnbondHeight+unbondingPeriodBlocks < block.Height { - toRelease = append(toRelease, val) - } - return false - }) - for _, val := range toRelease { - s.releaseValidator(val) - } - - // If any validators haven't signed in a while, - // unbond them, they have timed out. - toTimeout := []*Validator{} - s.BondedValidators.Iterate(func(index uint, val *Validator) bool { - lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight) - if lastActivityHeight+validatorTimeoutBlocks < block.Height { - log.Info("Validator timeout", "validator", val, "height", block.Height) - toTimeout = append(toTimeout, val) - } - return false - }) - for _, val := range toTimeout { - s.unbondValidator(val) - } - - // Increment validator AccumPowers - s.BondedValidators.IncrementAccum(1) - - s.LastBlockHeight = block.Height - s.LastBlockHash = block.Hash() - s.LastBlockParts = blockPartsHeader - s.LastBlockTime = block.Time - return nil -} - -// The returned Account is a copy, so mutating it -// has no side effects. -func (s *State) GetAccount(address []byte) *account.Account { - _, acc := s.accounts.Get(address) - if acc == nil { - return nil - } - return acc.(*account.Account).Copy() -} - -// The returned Account is a copy, so mutating it -// has no side effects. -func (s *State) GetAccounts() merkle.Tree { - return s.accounts.Copy() -} - -// The account is copied before setting, so mutating it -// afterwards has no side effects. -func (s *State) UpdateAccount(account *account.Account) { - s.accounts.Set(account.Address, account.Copy()) -} - -// The accounts are copied before setting, so mutating it -// afterwards has no side effects. -func (s *State) UpdateAccounts(accounts map[string]*account.Account) { - for _, acc := range accounts { - s.accounts.Set(acc.Address, acc.Copy()) - } -} - -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 { - _, valInfo := s.validatorInfos.Get(address) - if valInfo == nil { - return nil - } - return valInfo.(*ValidatorInfo).Copy() -} - -// Returns false if new, true if updated. -// The valInfo is copied before setting, so mutating it -// afterwards has no side effects. -func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) { - return s.validatorInfos.Set(valInfo.Address, valInfo.Copy()) -} - -// Returns a hash that represents the state data, -// excluding Last* -func (s *State) Hash() []byte { - hashables := []merkle.Hashable{ - s.BondedValidators, - s.UnbondingValidators, - s.accounts, - s.validatorInfos, - } - return merkle.HashFromHashables(hashables) +func (txErr InvalidTxError) Error() string { + return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason) } diff --git a/state/state_test.go b/state/state_test.go index 0d2c963bb..5f2990652 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -10,6 +10,17 @@ import ( "time" ) +func execTxWithState(state *State, tx types.Tx, runCall bool) error { + cache := NewBlockCache(state) + err := ExecTx(cache, tx, runCall) + if err != nil { + return err + } else { + cache.Sync() + return nil + } +} + func TestCopyState(t *testing.T) { // Generate a random state s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000) @@ -93,7 +104,7 @@ func TestGenesisSaveLoad(t *testing.T) { blockParts := block.MakePartSet() // Now append the block to s0. - err := s0.AppendBlock(block, blockParts.Header()) + err := ExecBlock(s0, block, blockParts.Header()) if err != nil { t.Error("Error appending initial block:", err) } @@ -182,7 +193,7 @@ func TestTxSequence(t *testing.T) { tx := makeSendTx(sequence) tx.Inputs[0].Signature = privAccounts[0].Sign(tx) stateCopy := state.Copy() - err := stateCopy.ExecTx(tx, true) + err := execTxWithState(stateCopy, tx, true) if i == 1 { // Sequence is good. if err != nil { @@ -241,7 +252,7 @@ func TestTxs(t *testing.T) { } tx.Inputs[0].Signature = privAccounts[0].Sign(tx) - err := state.ExecTx(tx, true) + err := execTxWithState(state, tx, true) if err != nil { t.Errorf("Got error in executing send transaction, %v", err) } @@ -278,7 +289,7 @@ func TestTxs(t *testing.T) { }, } tx.Inputs[0].Signature = privAccounts[0].Sign(tx) - err := state.ExecTx(tx, true) + err := execTxWithState(state, tx, true) if err != nil { t.Errorf("Got error in executing bond transaction, %v", err) } @@ -345,7 +356,7 @@ func TestAddValidator(t *testing.T) { } // Now append the block to s0. - err := s0.AppendBlock(block0, block0Parts.Header()) + err := ExecBlock(s0, block0, block0Parts.Header()) if err != nil { t.Error("Error appending initial block:", err) } @@ -379,7 +390,7 @@ func TestAddValidator(t *testing.T) { }, nil, ) block1Parts := block1.MakePartSet() - err = s0.AppendBlock(block1, block1Parts.Header()) + err = ExecBlock(s0, block1, block1Parts.Header()) if err != nil { t.Error("Error appending secondary block:", err) } diff --git a/state/tx_cache.go b/state/tx_cache.go new file mode 100644 index 000000000..1e7fd7139 --- /dev/null +++ b/state/tx_cache.go @@ -0,0 +1,193 @@ +package state + +import ( + ac "github.com/tendermint/tendermint/account" + . "github.com/tendermint/tendermint/common" + "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 { + 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 := RightPadWord256(NewContractAddress(creator.Address.Prefix(20), 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, + } + cache.accounts[addr] = vmAccountInfo{account, false} + return account + } else { + 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 and set cache + value = cache.backend.GetStorage(addr, key) + cache.storages[Tuple256{addr, key}] = value + return value +} + +// NOTE: Set value to zero to removed from the trie. +func (cache *TxCache) SetStorage(addr Word256, key Word256, value Word256) { + _, removed := vmUnpack(cache.accounts[addr]) + if removed { + panic("SetStorage() on a removed account") + } + 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.Prefix(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 uint64) []byte { + temp := make([]byte, 32+8) + copy(temp, caller) + PutUint64(temp[32:], nonce) + return sha3.Sha3(temp)[:20] +} + +// Converts backend.Account to vm.Account struct. +func toVMAccount(acc *ac.Account) *vm.Account { + return &vm.Account{ + Address: RightPadWord256(acc.Address), + Balance: acc.Balance, + Code: acc.Code, // This is crazy. + Nonce: uint64(acc.Sequence), + StorageRoot: RightPadWord256(acc.StorageRoot), + 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 = ac.PubKeyNil{} + } + var storageRoot []byte + if acc.StorageRoot.IsZero() { + storageRoot = nil + } else { + storageRoot = acc.StorageRoot.Bytes() + } + return &ac.Account{ + Address: acc.Address.Prefix(20), + PubKey: pubKey, + Balance: acc.Balance, + Code: acc.Code, + Sequence: uint(acc.Nonce), + StorageRoot: storageRoot, + } +} + +type vmAccountInfo struct { + account *vm.Account + removed bool +} + +func vmUnpack(accInfo vmAccountInfo) (*vm.Account, bool) { + return accInfo.account, accInfo.removed +} diff --git a/state/vm_app_state.go b/state/vm_app_state.go deleted file mode 100644 index 1005fc3dd..000000000 --- a/state/vm_app_state.go +++ /dev/null @@ -1,265 +0,0 @@ -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), - Other: acc.PubKey, - } -} - -// Converts vm.Account to state.Account struct. -func toStateAccount(acc *vm.Account) *ac.Account { - pubKey, ok := acc.Other.(ac.PubKey) - if !ok { - pubKey = ac.PubKeyNil{} - } - var storageRoot []byte - if acc.StorageRoot.IsZero() { - storageRoot = nil - } else { - storageRoot = acc.StorageRoot.Bytes() - } - return &ac.Account{ - Address: acc.Address.Address(), - PubKey: pubKey, - Balance: acc.Balance, - Code: acc.Code, - Sequence: uint(acc.Nonce), - StorageRoot: storageRoot, - } -} - -//----------------------------------------------------------------------------- - -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.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 - } - var storageRoot []byte - if currentAccount.StorageRoot.IsZero() { - storageRoot = nil - } else { - storageRoot = currentAccount.StorageRoot.Bytes() - } - storage.Load(storageRoot) - } - 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] -} diff --git a/vm/common.go b/vm/common.go deleted file mode 100644 index cf72ed6f9..000000000 --- a/vm/common.go +++ /dev/null @@ -1,35 +0,0 @@ -package vm - -import ( - "encoding/binary" -) - -func Uint64ToWord(i uint64) Word { - word := Word{} - PutUint64(word[:], i) - return word -} - -func BytesToWord(bz []byte) Word { - word := Word{} - copy(word[:], bz) - return word -} - -func LeftPadWord(bz []byte) (word Word) { - copy(word[32-len(bz):], bz) - return -} - -func RightPadWord(bz []byte) (word Word) { - copy(word[:], bz) - return -} - -func GetUint64(word Word) uint64 { - return binary.LittleEndian.Uint64(word[:]) -} - -func PutUint64(dest []byte, i uint64) { - binary.LittleEndian.PutUint64(dest, i) -} diff --git a/vm/gas.go b/vm/gas.go index 40a7b9a06..ebe5573a3 100644 --- a/vm/gas.go +++ b/vm/gas.go @@ -3,7 +3,6 @@ package vm const ( GasSha3 uint64 = 1 GasGetAccount uint64 = 1 - GasStorageCreate uint64 = 1 GasStorageUpdate uint64 = 1 GasStackOp uint64 = 1 diff --git a/vm/native.go b/vm/native.go index 467e0a022..ad9f3f3ae 100644 --- a/vm/native.go +++ b/vm/native.go @@ -3,19 +3,18 @@ package vm import ( "code.google.com/p/go.crypto/ripemd160" "crypto/sha256" + . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/vm/secp256k1" "github.com/tendermint/tendermint/vm/sha3" - - . "github.com/tendermint/tendermint/common" ) -var nativeContracts = make(map[Word]NativeContract) +var nativeContracts = make(map[Word256]NativeContract) func init() { - nativeContracts[Uint64ToWord(1)] = ecrecoverFunc - nativeContracts[Uint64ToWord(2)] = sha256Func - nativeContracts[Uint64ToWord(3)] = ripemd160Func - nativeContracts[Uint64ToWord(4)] = identityFunc + nativeContracts[Uint64ToWord256(1)] = ecrecoverFunc + nativeContracts[Uint64ToWord256(2)] = sha256Func + nativeContracts[Uint64ToWord256(3)] = ripemd160Func + nativeContracts[Uint64ToWord256(4)] = identityFunc } //----------------------------------------------------------------------------- diff --git a/vm/stack.go b/vm/stack.go index 4ac05ba9a..6b74643ac 100644 --- a/vm/stack.go +++ b/vm/stack.go @@ -2,11 +2,12 @@ package vm import ( "fmt" + . "github.com/tendermint/tendermint/common" ) // Not goroutine safe type Stack struct { - data []Word + data []Word256 ptr int gas *uint64 @@ -15,7 +16,7 @@ type Stack struct { func NewStack(capacity int, gas *uint64, err *error) *Stack { return &Stack{ - data: make([]Word, capacity), + data: make([]Word256, capacity), ptr: 0, gas: gas, err: err, @@ -36,7 +37,7 @@ func (st *Stack) setErr(err error) { } } -func (st *Stack) Push(d Word) { +func (st *Stack) Push(d Word256) { st.useGas(GasStackOp) if st.ptr == cap(st.data) { st.setErr(ErrDataStackOverflow) @@ -50,18 +51,18 @@ func (st *Stack) PushBytes(bz []byte) { if len(bz) != 32 { panic("Invalid bytes size: expected 32") } - st.Push(BytesToWord(bz)) + st.Push(RightPadWord256(bz)) } func (st *Stack) Push64(i uint64) { - st.Push(Uint64ToWord(i)) + st.Push(Uint64ToWord256(i)) } -func (st *Stack) Pop() Word { +func (st *Stack) Pop() Word256 { st.useGas(GasStackOp) if st.ptr == 0 { st.setErr(ErrDataStackUnderflow) - return Zero + return Zero256 } st.ptr-- return st.data[st.ptr] @@ -72,7 +73,7 @@ func (st *Stack) PopBytes() []byte { } func (st *Stack) Pop64() uint64 { - return GetUint64(st.Pop()) + return GetUint64(st.Pop().Bytes()) } func (st *Stack) Len() int { @@ -100,7 +101,7 @@ func (st *Stack) Dup(n int) { } // Not an opcode, costs no gas. -func (st *Stack) Peek() Word { +func (st *Stack) Peek() Word256 { return st.data[st.ptr-1] } diff --git a/vm/test/fake_app_state.go b/vm/test/fake_app_state.go index be11467ea..876a77cba 100644 --- a/vm/test/fake_app_state.go +++ b/vm/test/fake_app_state.go @@ -10,41 +10,39 @@ import ( type FakeAppState struct { accounts map[string]*Account - storage map[string]Word + storage map[string]Word256 logs []*Log } -func (fas *FakeAppState) GetAccount(addr Word) (*Account, error) { +func (fas *FakeAppState) GetAccount(addr Word256) *Account { account := fas.accounts[addr.String()] if account != nil { - return account, nil + return account } else { - return nil, Errorf("Invalid account addr: %v", addr) + panic(Fmt("Invalid account addr: %X", addr)) } } -func (fas *FakeAppState) UpdateAccount(account *Account) error { +func (fas *FakeAppState) UpdateAccount(account *Account) { _, ok := fas.accounts[account.Address.String()] if !ok { - return Errorf("Invalid account addr: %v", account.Address.String()) + panic(Fmt("Invalid account addr: %X", account.Address)) } else { // Nothing to do - return nil } } -func (fas *FakeAppState) DeleteAccount(account *Account) error { +func (fas *FakeAppState) RemoveAccount(account *Account) { _, ok := fas.accounts[account.Address.String()] if !ok { - return Errorf("Invalid account addr: %v", account.Address.String()) + panic(Fmt("Invalid account addr: %X", account.Address)) } else { - // Delete account + // Remove account delete(fas.accounts, account.Address.String()) - return nil } } -func (fas *FakeAppState) CreateAccount(creator *Account) (*Account, error) { +func (fas *FakeAppState) CreateAccount(creator *Account) *Account { addr := createAddress(creator) account := fas.accounts[addr.String()] if account == nil { @@ -53,36 +51,34 @@ func (fas *FakeAppState) CreateAccount(creator *Account) (*Account, error) { Balance: 0, Code: nil, Nonce: 0, - StorageRoot: Zero, - }, nil + StorageRoot: Zero256, + } } else { - return nil, Errorf("Invalid account addr: %v", addr) + panic(Fmt("Invalid account addr: %X", addr)) } } -func (fas *FakeAppState) GetStorage(addr Word, key Word) (Word, error) { +func (fas *FakeAppState) GetStorage(addr Word256, key Word256) Word256 { _, ok := fas.accounts[addr.String()] if !ok { - return Zero, Errorf("Invalid account addr: %v", addr) + panic(Fmt("Invalid account addr: %X", addr)) } value, ok := fas.storage[addr.String()+key.String()] if ok { - return value, nil + return value } else { - return Zero, nil + return Zero256 } } -func (fas *FakeAppState) SetStorage(addr Word, key Word, value Word) (bool, error) { +func (fas *FakeAppState) SetStorage(addr Word256, key Word256, value Word256) { _, ok := fas.accounts[addr.String()] if !ok { - return false, Errorf("Invalid account addr: %v", addr) + panic(Fmt("Invalid account addr: %X", addr)) } - _, ok = fas.storage[addr.String()+key.String()] fas.storage[addr.String()+key.String()] = value - return ok, nil } func (fas *FakeAppState) AddLog(log *Log) { @@ -92,23 +88,23 @@ func (fas *FakeAppState) AddLog(log *Log) { func main() { appState := &FakeAppState{ accounts: make(map[string]*Account), - storage: make(map[string]Word), + storage: make(map[string]Word256), logs: nil, } params := Params{ BlockHeight: 0, - BlockHash: Zero, + BlockHash: Zero256, BlockTime: 0, GasLimit: 0, } - ourVm := NewVM(appState, params, Zero) + ourVm := NewVM(appState, params, Zero256) // Create accounts account1 := &Account{ - Address: Uint64ToWord(100), + Address: Uint64ToWord256(100), } account2 := &Account{ - Address: Uint64ToWord(101), + Address: Uint64ToWord256(101), } var gas uint64 = 1000 @@ -117,11 +113,11 @@ func main() { } // Creates a 20 byte address and bumps the nonce. -func createAddress(creator *Account) Word { +func createAddress(creator *Account) Word256 { nonce := creator.Nonce creator.Nonce += 1 temp := make([]byte, 32+8) copy(temp, creator.Address[:]) PutUint64(temp[32:], nonce) - return RightPadWord(sha3.Sha3(temp)[:20]) + return RightPadWord256(sha3.Sha3(temp)[:20]) } diff --git a/vm/types.go b/vm/types.go index 968538ef1..443b72c63 100644 --- a/vm/types.go +++ b/vm/types.go @@ -1,44 +1,25 @@ package vm -import () +import ( + . "github.com/tendermint/tendermint/common" +) 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 + Address Word256 Balance uint64 Code []byte Nonce uint64 - StorageRoot Word + StorageRoot Word256 Other interface{} // For holding all other data. } type Log struct { - Address Word - Topics []Word + Address Word256 + Topics []Word256 Data []byte Height uint64 } @@ -46,14 +27,14 @@ type Log struct { type AppState interface { // Accounts - GetAccount(addr Word) (*Account, error) - UpdateAccount(*Account) error - DeleteAccount(*Account) error - CreateAccount(*Account) (*Account, error) + GetAccount(addr Word256) *Account + UpdateAccount(*Account) + RemoveAccount(*Account) + CreateAccount(*Account) *Account // Storage - GetStorage(Word, Word) (Word, error) - SetStorage(Word, Word, Word) (bool, error) // Setting to Zero is deleting. + GetStorage(Word256, Word256) Word256 + SetStorage(Word256, Word256, Word256) // Setting to Zero is deleting. // Logs AddLog(*Log) @@ -61,7 +42,7 @@ type AppState interface { type Params struct { BlockHeight uint64 - BlockHash Word + BlockHash Word256 BlockTime int64 GasLimit uint64 } diff --git a/vm/vm.go b/vm/vm.go index aa4c3d62e..8d5c15096 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -10,6 +10,7 @@ import ( ) var ( + ErrUnknownAddress = errors.New("Unknown address") ErrInsufficientBalance = errors.New("Insufficient balance") ErrInvalidJumpDest = errors.New("Invalid jump dest") ErrInsufficientGas = errors.New("Insuffient gas") @@ -32,12 +33,12 @@ const ( type VM struct { appState AppState params Params - origin Word + origin Word256 callDepth int } -func NewVM(appState AppState, params Params, origin Word) *VM { +func NewVM(appState AppState, params Params, origin Word256) *VM { return &VM{ appState: appState, params: params, @@ -114,7 +115,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case DIV: // 0x04 x, y := stack.Pop64(), stack.Pop64() if y == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0) } else { stack.Push64(x / y) @@ -124,7 +125,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case SDIV: // 0x05 x, y := int64(stack.Pop64()), int64(stack.Pop64()) if y == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0) } else { stack.Push64(uint64(x / y)) @@ -134,7 +135,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case MOD: // 0x06 x, y := stack.Pop64(), stack.Pop64() if y == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0) } else { stack.Push64(x % y) @@ -144,7 +145,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case SMOD: // 0x07 x, y := int64(stack.Pop64()), int64(stack.Pop64()) if y == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0) } else { stack.Push64(uint64(x % y)) @@ -154,7 +155,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case ADDMOD: // 0x08 x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64() if z == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) } else { stack.Push64(x % y) @@ -164,7 +165,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case MULMOD: // 0x09 x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64() if z == 0 { // TODO - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0) } else { stack.Push64(x % y) @@ -187,7 +188,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x < y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v < %v = %v\n", x, y, x < y) @@ -196,7 +197,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x > y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v > %v = %v\n", x, y, x > y) @@ -205,7 +206,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x < y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v < %v = %v\n", x, y, x < y) @@ -214,7 +215,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x > y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v > %v = %v\n", x, y, x > y) @@ -223,7 +224,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x > y { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v == %v = %v\n", x, y, x == y) @@ -232,7 +233,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if x == 0 { stack.Push64(1) } else { - stack.Push(Zero) + stack.Push(Zero256) } fmt.Printf(" %v == 0 = %v\n", x, x == 0) @@ -287,11 +288,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if ok = useGas(gas, GasGetAccount); !ok { return nil, firstErr(err, ErrInsufficientGas) } - account, err_ := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported. - if err_ != nil { - return nil, firstErr(err, err_) + acc := vm.appState.GetAccount(addr) // TODO ensure that 20byte lengths are supported. + if acc == nil { + return nil, firstErr(err, ErrUnknownAddress) } - balance := account.Balance + balance := acc.Balance stack.Push64(balance) fmt.Printf(" => %v (%X)\n", balance, addr) @@ -313,7 +314,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if !ok { return nil, firstErr(err, ErrInputOutOfBounds) } - stack.Push(RightPadWord(data)) + stack.Push(RightPadWord256(data)) fmt.Printf(" => 0x%X\n", data) case CALLDATASIZE: // 0x36 @@ -357,7 +358,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case GASPRICE_DEPRECATED: // 0x3A - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" => %X (GASPRICE IS DEPRECATED)\n") case EXTCODESIZE: // 0x3B @@ -365,11 +366,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if ok = useGas(gas, GasGetAccount); !ok { return nil, firstErr(err, ErrInsufficientGas) } - account, err_ := vm.appState.GetAccount(addr) - if err_ != nil { - return nil, firstErr(err, err_) + acc := vm.appState.GetAccount(addr) + if acc == nil { + return nil, firstErr(err, ErrUnknownAddress) } - code := account.Code + code := acc.Code l := uint64(len(code)) stack.Push64(l) fmt.Printf(" => %d\n", l) @@ -379,11 +380,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if ok = useGas(gas, GasGetAccount); !ok { return nil, firstErr(err, ErrInsufficientGas) } - account, err_ := vm.appState.GetAccount(addr) - if err_ != nil { - return nil, firstErr(err, err_) + acc := vm.appState.GetAccount(addr) + if acc == nil { + return nil, firstErr(err, ErrUnknownAddress) } - code := account.Code + code := acc.Code memOff := stack.Pop64() codeOff := stack.Pop64() length := stack.Pop64() @@ -399,11 +400,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case BLOCKHASH: // 0x40 - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case COINBASE: // 0x41 - stack.Push(Zero) + stack.Push(Zero256) fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case TIMESTAMP: // 0x42 @@ -430,7 +431,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if !ok { return nil, firstErr(err, ErrMemoryOutOfBounds) } - stack.Push(RightPadWord(data)) + stack.Push(RightPadWord256(data)) fmt.Printf(" => 0x%X\n", data) case MSTORE: // 0x52 @@ -452,21 +453,14 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case SLOAD: // 0x54 loc := stack.Pop() - data, _ := vm.appState.GetStorage(callee.Address, loc) + data := vm.appState.GetStorage(callee.Address, loc) stack.Push(data) fmt.Printf(" {0x%X : 0x%X}\n", loc, data) case SSTORE: // 0x55 loc, data := stack.Pop(), stack.Pop() - updated, err_ := vm.appState.SetStorage(callee.Address, loc, data) - if err = firstErr(err, err_); err != nil { - return nil, err - } - if updated { - useGas(gas, GasStorageUpdate) - } else { - useGas(gas, GasStorageCreate) - } + vm.appState.SetStorage(callee.Address, loc, data) + useGas(gas, GasStorageUpdate) fmt.Printf(" {0x%X : 0x%X}\n", loc, data) case JUMP: // 0x56 @@ -501,7 +495,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) } - res := RightPadWord(codeSegment) + res := RightPadWord256(codeSegment) stack.Push(res) pc += a fmt.Printf(" => 0x%X\n", res) @@ -518,7 +512,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case LOG0, LOG1, LOG2, LOG3, LOG4: n := int(op - LOG0) - topics := make([]Word, n) + topics := make([]Word256, n) offset, size := stack.Pop64(), stack.Pop64() for i := 0; i < n; i++ { topics[i] = stack.Pop() @@ -551,19 +545,14 @@ 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(callee) - if err != nil { - stack.Push(Zero) - fmt.Printf(" (*) 0x0 %v\n", err) + newAccount := vm.appState.CreateAccount(callee) + // Run the input to get the contract code. + ret, err_ := vm.Call(callee, newAccount, input, input, contractValue, gas) + if err_ != nil { + stack.Push(Zero256) } else { - // Run the input to get the contract code. - ret, err_ := vm.Call(callee, newAccount, input, input, contractValue, gas) - if err_ != nil { - stack.Push(Zero) - } else { - newAccount.Code = ret // Set the code - stack.Push(newAccount.Address) - } + newAccount.Code = ret // Set the code + stack.Push(newAccount.Address) } case CALL, CALLCODE: // 0xF1, 0xF2 @@ -598,22 +587,22 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if ok = useGas(gas, GasGetAccount); !ok { return nil, firstErr(err, ErrInsufficientGas) } - account, err_ := vm.appState.GetAccount(addr) - if err = firstErr(err, err_); err != nil { - return nil, err + acc := vm.appState.GetAccount(addr) + if acc == nil { + return nil, firstErr(err, ErrUnknownAddress) } if op == CALLCODE { - ret, err = vm.Call(callee, callee, account.Code, args, value, gas) + ret, err = vm.Call(callee, callee, acc.Code, args, value, gas) } else { - ret, err = vm.Call(callee, account, account.Code, args, value, gas) + ret, err = vm.Call(callee, acc, acc.Code, args, value, gas) } } // Push result if err != nil { - stack.Push(Zero) + stack.Push(Zero256) } else { - stack.Push(One) + stack.Push(One256) dest, ok := subslice(memory, retOffset, retSize, false) if !ok { return nil, firstErr(err, ErrMemoryOutOfBounds) @@ -640,15 +629,15 @@ 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 + // TODO if the receiver is Zero256, then make it the fee. + receiver := vm.appState.GetAccount(addr) + if receiver == nil { + return nil, firstErr(err, ErrUnknownAddress) } balance := callee.Balance receiver.Balance += balance vm.appState.UpdateAccount(receiver) - vm.appState.DeleteAccount(callee) + vm.appState.RemoveAccount(callee) fmt.Printf(" => (%X) %v\n", addr[:4], balance) fallthrough