Create BlockCache and TxCache for performance.

This commit is contained in:
Jae Kwon 2015-03-28 23:44:07 -07:00
parent fd0646fc4f
commit 79304b0dd3
21 changed files with 1345 additions and 1107 deletions

View File

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

View File

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

View File

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

View File

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

78
common/word.go Normal file
View File

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

View File

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

View File

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

196
state/block_cache.go Normal file
View File

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

18
state/common.go Normal file
View File

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

593
state/execution.go Normal file
View File

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

View File

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

View File

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

193
state/tx_cache.go Normal file
View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ package vm
const (
GasSha3 uint64 = 1
GasGetAccount uint64 = 1
GasStorageCreate uint64 = 1
GasStorageUpdate uint64 = 1
GasStackOp uint64 = 1

View File

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

View File

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

View File

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

View File

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

123
vm/vm.go
View File

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