Create BlockCache and TxCache for performance.
This commit is contained in:
parent
fd0646fc4f
commit
79304b0dd3
|
@ -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) {
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) }
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
724
state/state.go
724
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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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]
|
||||
}
|
35
vm/common.go
35
vm/common.go
|
@ -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)
|
||||
}
|
|
@ -3,7 +3,6 @@ package vm
|
|||
const (
|
||||
GasSha3 uint64 = 1
|
||||
GasGetAccount uint64 = 1
|
||||
GasStorageCreate uint64 = 1
|
||||
GasStorageUpdate uint64 = 1
|
||||
|
||||
GasStackOp uint64 = 1
|
||||
|
|
13
vm/native.go
13
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
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
19
vm/stack.go
19
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]
|
||||
}
|
||||
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
47
vm/types.go
47
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
|
||||
}
|
||||
|
|
123
vm/vm.go
123
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
|
||||
|
||||
|
|
Loading…
Reference in New Issue