Merge pull request #3 from tendermint/rpc_rebase

Rpc rebase
This commit is contained in:
Jae Kwon 2015-03-29 19:08:45 -07:00
commit df4eac1cc8
47 changed files with 2716 additions and 1454 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
.bak
tendermint
.DS_Store
rpc/test/.tendermint

View File

@ -38,13 +38,13 @@ type Account struct {
StorageRoot []byte // VM storage merkle root.
}
func (account *Account) Copy() *Account {
accountCopy := *account
return &accountCopy
func (acc *Account) Copy() *Account {
accCopy := *acc
return &accCopy
}
func (account *Account) String() string {
return fmt.Sprintf("Account{%X:%v C:%v S:%X}", account.Address, account.PubKey, len(account.Code), account.StorageRoot)
func (acc *Account) String() string {
return fmt.Sprintf("Account{%X:%v C:%v S:%X}", acc.Address, acc.PubKey, len(acc.Code), acc.StorageRoot)
}
func AccountEncoder(o interface{}, w io.Writer, n *int64, err *error) {

View File

@ -25,6 +25,18 @@ func GenPrivAccount() *PrivAccount {
}
}
func GenPrivAccountFromKey(privKeyBytes [64]byte) *PrivAccount {
pubKeyBytes := ed25519.MakePublicKey(&privKeyBytes)
pubKey := PubKeyEd25519(pubKeyBytes[:])
privKey := PrivKeyEd25519(privKeyBytes[:])
return &PrivAccount{
Address: pubKey.Address(),
PubKey: pubKey,
PrivKey: privKey,
}
}
func (privAccount *PrivAccount) Sign(o Signable) Signature {
return privAccount.PrivKey.Sign(SignBytes(o))
}

View File

@ -2,6 +2,7 @@ package binary
import (
"bytes"
"fmt"
"reflect"
"testing"
"time"
@ -58,6 +59,35 @@ var _ = RegisterInterface(
ConcreteType{&Viper{}},
)
func TestAnimalInterface(t *testing.T) {
var foo Animal
// Type of pointer to Animal
rt := reflect.TypeOf(&foo)
fmt.Printf("rt: %v\n", rt)
// Type of Animal itself.
// NOTE: normally this is acquired through other means
// like introspecting on method signatures, or struct fields.
rte := rt.Elem()
fmt.Printf("rte: %v\n", rte)
// Get a new pointer to the interface
// NOTE: calling .Interface() is to get the actual value,
// instead of reflection values.
ptr := reflect.New(rte).Interface()
fmt.Printf("ptr: %v", ptr)
// Make a binary byteslice that represents a snake.
snakeBytes := BinaryBytes(Snake([]byte("snake")))
snakeReader := bytes.NewReader(snakeBytes)
// Now you can read it.
n, err := new(int64), new(error)
it := *ReadBinary(ptr, snakeReader, n, err).(*Animal)
fmt.Println(it, reflect.TypeOf(it))
}
//-------------------------------------
type Constructor func() interface{}
@ -287,9 +317,9 @@ func validateComplexArray(o interface{}, t *testing.T) {
var testCases = []TestCase{}
func init() {
//testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic})
//testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex})
//testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2})
testCases = append(testCases, TestCase{constructBasic, instantiateBasic, validateBasic})
testCases = append(testCases, TestCase{constructComplex, instantiateComplex, validateComplex})
testCases = append(testCases, TestCase{constructComplex2, instantiateComplex2, validateComplex2})
testCases = append(testCases, TestCase{constructComplexArray, instantiateComplexArray, validateComplexArray})
}

View File

@ -194,7 +194,7 @@ FOR_LOOP:
break SYNC_LOOP
} else {
bcR.pool.PopRequest()
err := bcR.state.AppendBlock(first, firstPartsHeader)
err := sm.ExecBlock(bcR.state, first, firstPartsHeader)
if err != nil {
// TODO This is bad, are we zombie?
panic(Fmt("Failed to process committed block: %v", err))

View File

@ -1,6 +1,7 @@
package common
import (
"encoding/binary"
"sort"
)
@ -18,3 +19,13 @@ func SearchUint64s(a []uint64, x uint64) int {
}
func (p Uint64Slice) Search(x uint64) int { return SearchUint64s(p, x) }
//-----------------------------------------------------------------------------
func PutUint64(dest []byte, i uint64) {
binary.LittleEndian.PutUint64(dest, i)
}
func GetUint64(src []byte) uint64 {
return binary.LittleEndian.Uint64(src)
}

78
common/word.go Normal file
View File

@ -0,0 +1,78 @@
package common
import (
"bytes"
"encoding/binary"
"sort"
)
var (
Zero256 = Word256{0}
One256 = Word256{1}
)
type Word256 [32]byte
func (w Word256) String() string { return string(w[:]) }
func (w Word256) Copy() Word256 { return w }
func (w Word256) Bytes() []byte { return w[:] } // copied.
func (w Word256) Prefix(n int) []byte { return w[:n] }
func (w Word256) IsZero() bool {
accum := byte(0)
for _, byt := range w {
accum |= byt
}
return accum == 0
}
func (w Word256) Compare(other Word256) int {
return bytes.Compare(w[:], other[:])
}
func Uint64ToWord256(i uint64) Word256 {
word := Word256{}
PutUint64(word[:], i)
return word
}
func RightPadWord256(bz []byte) (word Word256) {
copy(word[:], bz)
return
}
func LeftPadWord256(bz []byte) (word Word256) {
copy(word[32-len(bz):], bz)
return
}
func Uint64FromWord256(word Word256) uint64 {
return binary.LittleEndian.Uint64(word[:])
}
//-------------------------------------
type Tuple256 struct {
First Word256
Second Word256
}
func (tuple Tuple256) Compare(other Tuple256) int {
firstCompare := tuple.First.Compare(other.First)
if firstCompare == 0 {
return tuple.Second.Compare(other.Second)
} else {
return firstCompare
}
}
func Tuple256Split(t Tuple256) (Word256, Word256) {
return t.First, t.Second
}
type Tuple256Slice []Tuple256
func (p Tuple256Slice) Len() int { return len(p) }
func (p Tuple256Slice) Less(i, j int) bool {
return p[i].Compare(p[j]) < 0
}
func (p Tuple256Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Tuple256Slice) Sort() { sort.Sort(p) }

View File

@ -1015,7 +1015,8 @@ func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartS
}
// Already staged?
if cs.stagedBlock == block {
blockHash := block.Hash()
if cs.stagedBlock != nil && len(blockHash) != 0 && bytes.Equal(cs.stagedBlock.Hash(), blockHash) {
return nil
}
@ -1024,7 +1025,7 @@ func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartS
// Commit block onto the copied state.
// NOTE: Basic validation is done in state.AppendBlock().
err := stateCopy.AppendBlock(block, blockParts.Header())
err := sm.ExecBlock(stateCopy, block, blockParts.Header())
if err != nil {
return err
} else {

View File

@ -12,6 +12,7 @@ import (
mempl "github.com/tendermint/tendermint/mempool"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/rpc"
"github.com/tendermint/tendermint/rpc/core"
sm "github.com/tendermint/tendermint/state"
)
@ -150,13 +151,17 @@ func (n *Node) DialSeed() {
}
func (n *Node) StartRpc() {
rpc.SetRPCBlockStore(n.blockStore)
rpc.SetRPCConsensusState(n.consensusState)
rpc.SetRPCMempoolReactor(n.mempoolReactor)
rpc.SetRPCSwitch(n.sw)
core.SetBlockStore(n.blockStore)
core.SetConsensusState(n.consensusState)
core.SetMempoolReactor(n.mempoolReactor)
core.SetSwitch(n.sw)
rpc.StartHTTPServer()
}
func (n *Node) Switch() *p2p.Switch {
return n.sw
}
func (n *Node) ConsensusState() *consensus.ConsensusState {
return n.consensusState
}

View File

@ -19,12 +19,14 @@ import (
type Mempool struct {
mtx sync.Mutex
state *sm.State
cache *sm.BlockCache
txs []types.Tx
}
func NewMempool(state *sm.State) *Mempool {
return &Mempool{
state: state,
cache: sm.NewBlockCache(state),
}
}
@ -32,11 +34,15 @@ func (mem *Mempool) GetState() *sm.State {
return mem.state
}
func (mem *Mempool) GetCache() *sm.BlockCache {
return mem.cache
}
// Apply tx to the state and remember it.
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 +68,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 +93,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)

View File

@ -1,61 +0,0 @@
package rpc
import (
"net/http"
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
)
func GenPrivAccountHandler(w http.ResponseWriter, r *http.Request) {
privAccount := account.GenPrivAccount()
WriteAPIResponse(w, API_OK, struct {
PrivAccount *account.PrivAccount
}{privAccount})
}
//-----------------------------------------------------------------------------
func GetAccountHandler(w http.ResponseWriter, r *http.Request) {
addressStr := GetParam(r, "address")
var address []byte
var err error
binary.ReadJSON(&address, []byte(addressStr), &err)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid address: %v", err))
return
}
state := consensusState.GetState()
account_ := state.GetAccount(address)
if account_ == nil {
WriteAPIResponse(w, API_OK, struct{}{})
return
}
WriteAPIResponse(w, API_OK, struct {
Account *account.Account
}{account_})
}
//-----------------------------------------------------------------------------
func ListAccountsHandler(w http.ResponseWriter, r *http.Request) {
var blockHeight uint
var accounts []*account.Account
state := consensusState.GetState()
blockHeight = state.LastBlockHeight
state.GetAccounts().Iterate(func(key interface{}, value interface{}) bool {
accounts = append(accounts, value.(*account.Account))
return false
})
WriteAPIResponse(w, API_OK, struct {
BlockHeight uint
Accounts []*account.Account
}{blockHeight, accounts})
}

32
rpc/core/accounts.go Normal file
View File

@ -0,0 +1,32 @@
package core
import (
"github.com/tendermint/tendermint/account"
)
//-----------------------------------------------------------------------------
func GenPrivAccount() (*ResponseGenPrivAccount, error) {
return &ResponseGenPrivAccount{account.GenPrivAccount()}, nil
}
//-----------------------------------------------------------------------------
func GetAccount(address []byte) (*ResponseGetAccount, error) {
cache := mempoolReactor.Mempool.GetCache()
return &ResponseGetAccount{cache.GetAccount(address)}, nil
}
//-----------------------------------------------------------------------------
func ListAccounts() (*ResponseListAccounts, error) {
var blockHeight uint
var accounts []*account.Account
state := consensusState.GetState()
blockHeight = state.LastBlockHeight
state.GetAccounts().Iterate(func(key interface{}, value interface{}) bool {
accounts = append(accounts, value.(*account.Account))
return false
})
return &ResponseListAccounts{blockHeight, accounts}, nil
}

View File

@ -1,15 +1,14 @@
package rpc
package core
import (
"net/http"
"fmt"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/types"
)
func BlockchainInfoHandler(w http.ResponseWriter, r *http.Request) {
minHeight, _ := GetParamUint(r, "min_height")
maxHeight, _ := GetParamUint(r, "max_height")
//-----------------------------------------------------------------------------
func BlockchainInfo(minHeight, maxHeight uint) (*ResponseBlockchainInfo, error) {
if maxHeight == 0 {
maxHeight = blockStore.Height()
} else {
@ -26,30 +25,20 @@ func BlockchainInfoHandler(w http.ResponseWriter, r *http.Request) {
blockMetas = append(blockMetas, blockMeta)
}
WriteAPIResponse(w, API_OK, struct {
LastHeight uint
BlockMetas []*types.BlockMeta
}{blockStore.Height(), blockMetas})
return &ResponseBlockchainInfo{blockStore.Height(), blockMetas}, nil
}
//-----------------------------------------------------------------------------
func GetBlockHandler(w http.ResponseWriter, r *http.Request) {
height, _ := GetParamUint(r, "height")
func GetBlock(height uint) (*ResponseGetBlock, error) {
if height == 0 {
WriteAPIResponse(w, API_INVALID_PARAM, "height must be greater than 1")
return
return nil, fmt.Errorf("height must be greater than 1")
}
if height > blockStore.Height() {
WriteAPIResponse(w, API_INVALID_PARAM, "height must be less than the current blockchain height")
return
return nil, fmt.Errorf("height must be less than the current blockchain height")
}
blockMeta := blockStore.LoadBlockMeta(height)
block := blockStore.LoadBlock(height)
WriteAPIResponse(w, API_OK, struct {
BlockMeta *types.BlockMeta
Block *types.Block
}{blockMeta, block})
return &ResponseGetBlock{blockMeta, block}, nil
}

7
rpc/core/log.go Normal file
View File

@ -0,0 +1,7 @@
package core
import (
"github.com/tendermint/log15"
)
var log = log15.New("module", "rpc")

41
rpc/core/mempool.go Normal file
View File

@ -0,0 +1,41 @@
package core
import (
"fmt"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
//-----------------------------------------------------------------------------
type Receipt struct {
TxHash []byte
CreatesContract uint8
ContractAddr []byte
}
// pass pointer?
// Note: tx must be signed
func BroadcastTx(tx types.Tx) (*ResponseBroadcastTx, error) {
err := mempoolReactor.BroadcastTx(tx)
if err != nil {
return nil, fmt.Errorf("Error broadcasting transaction: %v", err)
}
txHash := types.TxId(tx)
var createsContract uint8
var contractAddr []byte
// check if creates new contract
if callTx, ok := tx.(*types.CallTx); ok {
if callTx.Address == nil {
createsContract = 1
contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence))
}
}
return &ResponseBroadcastTx{Receipt{txHash, createsContract, contractAddr}}, nil
}
/*
curl -H 'content-type: text/plain;' http://127.0.0.1:8888/submit_tx?tx=...
*/

39
rpc/core/net.go Normal file
View File

@ -0,0 +1,39 @@
package core
import (
"github.com/tendermint/tendermint/config"
dbm "github.com/tendermint/tendermint/db"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
//-----------------------------------------------------------------------------
func Status() (*ResponseStatus, error) {
db := dbm.NewMemDB()
genesisState := sm.MakeGenesisStateFromFile(db, config.App().GetString("GenesisFile"))
genesisHash := genesisState.Hash()
latestHeight := blockStore.Height()
var (
latestBlockMeta *types.BlockMeta
latestBlockHash []byte
latestBlockTime int64
)
if latestHeight != 0 {
latestBlockMeta = blockStore.LoadBlockMeta(latestHeight)
latestBlockHash = latestBlockMeta.Hash
latestBlockTime = latestBlockMeta.Header.Time.UnixNano()
}
return &ResponseStatus{genesisHash, config.App().GetString("Network"), latestBlockHash, latestHeight, latestBlockTime}, nil
}
//-----------------------------------------------------------------------------
func NetInfo() (*ResponseNetInfo, error) {
o, i, _ := p2pSwitch.NumPeers()
numPeers := o + i
listening := p2pSwitch.IsListening()
network := config.App().GetString("Network")
return &ResponseNetInfo{numPeers, listening, network}, nil
}

View File

@ -1,4 +1,4 @@
package rpc
package core
import (
bc "github.com/tendermint/tendermint/blockchain"
@ -12,18 +12,18 @@ var consensusState *consensus.ConsensusState
var mempoolReactor *mempl.MempoolReactor
var p2pSwitch *p2p.Switch
func SetRPCBlockStore(bs *bc.BlockStore) {
func SetBlockStore(bs *bc.BlockStore) {
blockStore = bs
}
func SetRPCConsensusState(cs *consensus.ConsensusState) {
func SetConsensusState(cs *consensus.ConsensusState) {
consensusState = cs
}
func SetRPCMempoolReactor(mr *mempl.MempoolReactor) {
func SetMempoolReactor(mr *mempl.MempoolReactor) {
mempoolReactor = mr
}
func SetRPCSwitch(sw *p2p.Switch) {
func SetSwitch(sw *p2p.Switch) {
p2pSwitch = sw
}

59
rpc/core/responses.go Normal file
View File

@ -0,0 +1,59 @@
package core
import (
"github.com/tendermint/tendermint/account"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
type ResponseGenPrivAccount struct {
PrivAccount *account.PrivAccount
}
type ResponseGetAccount struct {
Account *account.Account
}
type ResponseListAccounts struct {
BlockHeight uint
Accounts []*account.Account
}
type ResponseBlockchainInfo struct {
LastHeight uint
BlockMetas []*types.BlockMeta
}
type ResponseGetBlock struct {
BlockMeta *types.BlockMeta
Block *types.Block
}
// curl -H 'content-type: text/plain;' http://127.0.0.1:8888/submit_tx?tx=...
type ResponseBroadcastTx struct {
Receipt Receipt
}
type ResponseStatus struct {
GenesisHash []byte
Network string
LatestBlockHash []byte
LatestBlockHeight uint
LatestBlockTime int64 // nano
}
type ResponseNetInfo struct {
NumPeers int
Listening bool
Network string
}
type ResponseSignTx struct {
Tx types.Tx
}
type ResponseListValidators struct {
BlockHeight uint
BondedValidators []*sm.Validator
UnbondingValidators []*sm.Validator
}

View File

@ -1,37 +1,21 @@
package rpc
package core
import (
"net/http"
"fmt"
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/types"
)
func SignTxHandler(w http.ResponseWriter, r *http.Request) {
txStr := GetParam(r, "tx")
privAccountsStr := GetParam(r, "privAccounts")
//-----------------------------------------------------------------------------
func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ResponseSignTx, error) {
// more checks?
var err error
var tx types.Tx
binary.ReadJSON(&tx, []byte(txStr), &err)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid tx: %v", err))
return
}
privAccounts := binary.ReadJSON([]*account.PrivAccount{}, []byte(privAccountsStr), &err).([]*account.PrivAccount)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid privAccounts: %v", err))
return
}
for i, privAccount := range privAccounts {
if privAccount == nil || privAccount.PrivKey == nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid (empty) privAccount @%v", i))
return
return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i)
}
}
switch tx.(type) {
case *types.SendTx:
sendTx := tx.(*types.SendTx)
@ -39,6 +23,10 @@ func SignTxHandler(w http.ResponseWriter, r *http.Request) {
input.PubKey = privAccounts[i].PubKey
input.Signature = privAccounts[i].Sign(sendTx)
}
case *types.CallTx:
callTx := tx.(*types.CallTx)
callTx.Input.PubKey = privAccounts[0].PubKey
callTx.Input.Signature = privAccounts[0].Sign(callTx)
case *types.BondTx:
bondTx := tx.(*types.BondTx)
for i, input := range bondTx.Inputs {
@ -52,6 +40,5 @@ func SignTxHandler(w http.ResponseWriter, r *http.Request) {
rebondTx := tx.(*types.RebondTx)
rebondTx.Signature = privAccounts[0].Sign(rebondTx).(account.SignatureEd25519)
}
WriteAPIResponse(w, API_OK, struct{ types.Tx }{tx})
return &ResponseSignTx{tx}, nil
}

View File

@ -1,12 +1,12 @@
package rpc
package core
import (
"net/http"
sm "github.com/tendermint/tendermint/state"
)
func ListValidatorsHandler(w http.ResponseWriter, r *http.Request) {
//-----------------------------------------------------------------------------
func ListValidators() (*ResponseListValidators, error) {
var blockHeight uint
var bondedValidators []*sm.Validator
var unbondingValidators []*sm.Validator
@ -22,9 +22,5 @@ func ListValidatorsHandler(w http.ResponseWriter, r *http.Request) {
return false
})
WriteAPIResponse(w, API_OK, struct {
BlockHeight uint
BondedValidators []*sm.Validator
UnbondingValidators []*sm.Validator
}{blockHeight, bondedValidators, unbondingValidators})
return &ResponseListValidators{blockHeight, bondedValidators, unbondingValidators}, nil
}

View File

@ -1,21 +1,277 @@
package rpc
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/tendermint/tendermint/binary"
"github.com/tendermint/tendermint/rpc/core"
"io/ioutil"
"net/http"
"reflect"
"strconv"
)
// cache all type information about each function up front
// (func, responseStruct, argNames)
// XXX: response structs are allocated once and reused - will this cause an issue eg. if a field ever not overwritten?
var funcMap = map[string]*FuncWrapper{
"status": funcWrap(core.Status, []string{}),
"net_info": funcWrap(core.NetInfo, []string{}),
"blockchain": funcWrap(core.BlockchainInfo, []string{"min_height", "max_height"}),
"get_block": funcWrap(core.GetBlock, []string{"height"}),
"get_account": funcWrap(core.GetAccount, []string{"address"}),
"list_validators": funcWrap(core.ListValidators, []string{}),
"broadcast_tx": funcWrap(core.BroadcastTx, []string{"tx"}),
"list_accounts": funcWrap(core.ListAccounts, []string{}),
"unsafe/gen_priv_account": funcWrap(core.GenPrivAccount, []string{}),
"unsafe/sign_tx": funcWrap(core.SignTx, []string{"tx", "privAccounts"}),
}
// holds all type information for each function
type FuncWrapper struct {
f reflect.Value // function from "rpc/core"
args []reflect.Type // type of each function arg
returns []reflect.Type // type of each return arg
argNames []string // name of each argument
}
func funcWrap(f interface{}, args []string) *FuncWrapper {
return &FuncWrapper{
f: reflect.ValueOf(f),
args: funcArgTypes(f),
returns: funcReturnTypes(f),
argNames: args,
}
}
// convert from a function name to the http handler
func toHandler(funcName string) func(http.ResponseWriter, *http.Request) {
funcInfo := funcMap[funcName]
return func(w http.ResponseWriter, r *http.Request) {
values, err := queryToValues(funcInfo, r)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error())
return
}
returns := funcInfo.f.Call(values)
response, err := returnsToResponse(funcInfo, returns)
if err != nil {
WriteAPIResponse(w, API_ERROR, nil, err.Error())
return
}
WriteAPIResponse(w, API_OK, response, "")
}
}
// convert a (json) string to a given type
func jsonToArg(ty reflect.Type, arg string) (reflect.Value, error) {
v := reflect.New(ty).Elem()
kind := v.Kind()
var err error
switch kind {
case reflect.Interface:
v = reflect.New(ty)
binary.ReadJSON(v.Interface(), []byte(arg), &err)
if err != nil {
return v, err
}
v = v.Elem()
case reflect.Struct:
binary.ReadJSON(v.Interface(), []byte(arg), &err)
if err != nil {
return v, err
}
case reflect.Slice:
rt := ty.Elem()
if rt.Kind() == reflect.Uint8 {
// if hex, decode
if len(arg) > 2 && arg[:2] == "0x" {
arg = arg[2:]
b, err := hex.DecodeString(arg)
if err != nil {
return v, err
}
v = reflect.ValueOf(b)
} else {
v = reflect.ValueOf([]byte(arg))
}
} else {
v = reflect.New(ty)
binary.ReadJSON(v.Interface(), []byte(arg), &err)
if err != nil {
return v, err
}
v = v.Elem()
}
case reflect.Int64:
u, err := strconv.ParseInt(arg, 10, 64)
if err != nil {
return v, err
}
v = reflect.ValueOf(u)
case reflect.Int32:
u, err := strconv.ParseInt(arg, 10, 32)
if err != nil {
return v, err
}
v = reflect.ValueOf(u)
case reflect.Uint64:
u, err := strconv.ParseUint(arg, 10, 64)
if err != nil {
return v, err
}
v = reflect.ValueOf(u)
case reflect.Uint:
u, err := strconv.ParseUint(arg, 10, 32)
if err != nil {
return v, err
}
v = reflect.ValueOf(u)
default:
v = reflect.ValueOf(arg)
}
return v, nil
}
// covert an http query to a list of properly typed values.
// to be properly decoded the arg must be a concrete type from tendermint (if its an interface).
func queryToValues(funcInfo *FuncWrapper, r *http.Request) ([]reflect.Value, error) {
argTypes := funcInfo.args
argNames := funcInfo.argNames
var err error
values := make([]reflect.Value, len(argNames))
for i, name := range argNames {
ty := argTypes[i]
arg := GetParam(r, name)
values[i], err = jsonToArg(ty, arg)
if err != nil {
return nil, err
}
}
return values, nil
}
// covert a list of interfaces to properly typed values
// TODO!
func paramsToValues(funcInfo *FuncWrapper, params []string) ([]reflect.Value, error) {
values := make([]reflect.Value, len(params))
for i, p := range params {
ty := funcInfo.args[i]
v, err := jsonToArg(ty, p)
if err != nil {
return nil, err
}
values[i] = v
}
return values, nil
}
// returns is Response struct and error. If error is not nil, return it
func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interface{}, error) {
errV := returns[1]
if errV.Interface() != nil {
return nil, fmt.Errorf("%v", errV.Interface())
}
return returns[0].Interface(), nil
}
/*
// convert a list of values to a populated struct with the correct types
func returnsToResponse(funcInfo *FuncWrapper, returns []reflect.Value) (interface{}, error) {
returnTypes := funcInfo.returns
finalType := returnTypes[len(returnTypes)-1]
if finalType.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
errV := returns[len(returnTypes)-1]
if errV.Interface() != nil {
return nil, fmt.Errorf("%v", errV.Interface())
}
}
// copy the response struct (New returns a pointer so we have to Elem() twice)
v := reflect.New(funcInfo.response.Elem().Type()).Elem()
nFields := v.NumField()
for i := 0; i < nFields; i++ {
field := v.FieldByIndex([]int{i})
field.Set(returns[i])
}
return v.Interface(), nil
}*/
// jsonrpc calls grab the given method's function info and runs reflect.Call
func JsonRpcHandler(w http.ResponseWriter, r *http.Request) {
b, _ := ioutil.ReadAll(r.Body)
var jrpc JsonRpc
err := json.Unmarshal(b, &jrpc)
if err != nil {
// TODO
}
funcInfo := funcMap[jrpc.Method]
values, err := paramsToValues(funcInfo, jrpc.Params)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, nil, err.Error())
return
}
returns := funcInfo.f.Call(values)
response, err := returnsToResponse(funcInfo, returns)
if err != nil {
WriteAPIResponse(w, API_ERROR, nil, err.Error())
return
}
WriteAPIResponse(w, API_OK, response, "")
}
func initHandlers() {
http.HandleFunc("/status", StatusHandler)
http.HandleFunc("/net_info", NetInfoHandler)
http.HandleFunc("/blockchain", BlockchainInfoHandler)
http.HandleFunc("/get_block", GetBlockHandler)
http.HandleFunc("/get_account", GetAccountHandler)
http.HandleFunc("/list_validators", ListValidatorsHandler)
http.HandleFunc("/broadcast_tx", BroadcastTxHandler)
// HTTP endpoints
// toHandler runs once for each function and caches
// all reflection data
http.HandleFunc("/status", toHandler("status"))
http.HandleFunc("/net_info", toHandler("net_info"))
http.HandleFunc("/blockchain", toHandler("blockchain"))
http.HandleFunc("/get_block", toHandler("get_block"))
http.HandleFunc("/get_account", toHandler("get_account"))
http.HandleFunc("/list_validators", toHandler("list_validators"))
http.HandleFunc("/broadcast_tx", toHandler("broadcast_tx"))
http.HandleFunc("/list_accounts", toHandler("list_accounts"))
http.HandleFunc("/unsafe/gen_priv_account", toHandler("unsafe/gen_priv_account"))
http.HandleFunc("/unsafe/sign_tx", toHandler("unsafe/sign_tx"))
//http.HandleFunc("/call", CallHandler)
//http.HandleFunc("/get_storage", GetStorageHandler)
http.HandleFunc("/develop/gen_priv_account", GenPrivAccountHandler)
http.HandleFunc("/develop/list_accounts", ListAccountsHandler)
http.HandleFunc("/develop/sign_tx", SignTxHandler)
// JsonRPC endpoints
http.HandleFunc("/", JsonRpcHandler)
// unsafe JsonRPC endpoints
//http.HandleFunc("/unsafe", UnsafeJsonRpcHandler)
}
type JsonRpc struct {
JsonRpc string `json:"jsonrpc"`
Method string `json:"method"`
Params []string `json:"params"`
Id int `json:"id"`
}
// this will panic if not passed a function
func funcArgTypes(f interface{}) []reflect.Type {
t := reflect.TypeOf(f)
n := t.NumIn()
types := make([]reflect.Type, n)
for i := 0; i < n; i++ {
types[i] = t.In(i)
}
return types
}
func funcReturnTypes(f interface{}) []reflect.Type {
t := reflect.TypeOf(f)
n := t.NumOut()
types := make([]reflect.Type, n)
for i := 0; i < n; i++ {
types[i] = t.Out(i)
}
return types
}

View File

@ -24,7 +24,7 @@ var (
)
func panicAPI(err error) {
panic(APIResponse{API_INVALID_PARAM, err.Error()})
panic(APIResponse{API_INVALID_PARAM, nil, err.Error()})
}
func GetParam(r *http.Request, param string) string {

View File

@ -38,16 +38,22 @@ const (
type APIResponse struct {
Status APIStatus `json:"status"`
Data interface{} `json:"data"`
Error string `json:"error"`
}
func (res APIResponse) Error() string {
return fmt.Sprintf("Status(%v) %v", res.Status, res.Data)
func (res APIResponse) StatusError() string {
return fmt.Sprintf("Status(%v) %v", res.Status, res.Error)
}
func WriteAPIResponse(w http.ResponseWriter, status APIStatus, data interface{}) {
func WriteAPIResponse(w http.ResponseWriter, status APIStatus, data interface{}, responseErr string) {
res := APIResponse{}
res.Status = status
if data == nil {
// so json doesn't vommit
data = struct{}{}
}
res.Data = data
res.Error = responseErr
buf, n, err := new(bytes.Buffer), new(int64), new(error)
binary.WriteJSON(res, buf, n, err)
@ -109,7 +115,7 @@ func RecoverAndLogHandler(handler http.Handler) http.Handler {
// If APIResponse,
if res, ok := e.(APIResponse); ok {
WriteAPIResponse(rww, res.Status, res.Data)
WriteAPIResponse(rww, res.Status, nil, res.Error)
} else {
// For the rest,
rww.WriteHeader(http.StatusInternalServerError)

View File

@ -1,50 +0,0 @@
package rpc
import (
"net/http"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
func BroadcastTxHandler(w http.ResponseWriter, r *http.Request) {
txJSON := GetParam(r, "tx")
var err error
var tx types.Tx
binary.ReadJSON(&tx, []byte(txJSON), &err)
if err != nil {
WriteAPIResponse(w, API_INVALID_PARAM, Fmt("Invalid tx: %v", err))
return
}
err = mempoolReactor.BroadcastTx(tx)
if err != nil {
WriteAPIResponse(w, API_ERROR, Fmt("Error broadcasting transaction: %v", err))
return
}
txHash := merkle.HashFromBinary(tx)
var createsContract bool
var contractAddr []byte
if callTx, ok := tx.(*types.CallTx); ok {
if callTx.Address == nil {
createsContract = true
contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence))
}
}
WriteAPIResponse(w, API_OK, struct {
TxHash []byte
CreatesContract bool
ContractAddr []byte
}{txHash, createsContract, contractAddr})
return
}
/*
curl -H 'content-type: text/plain;' http://127.0.0.1:8888/submit_tx?tx=...
*/

View File

@ -1,33 +0,0 @@
package rpc
import (
"github.com/tendermint/tendermint/config"
"net/http"
)
func StatusHandler(w http.ResponseWriter, r *http.Request) {
genesisHash := blockStore.LoadBlockMeta(0).Hash
latestHeight := blockStore.Height()
latestBlockMeta := blockStore.LoadBlockMeta(latestHeight)
latestBlockHash := latestBlockMeta.Hash
latestBlockTime := latestBlockMeta.Header.Time.UnixNano()
WriteAPIResponse(w, API_OK, struct {
GenesisHash []byte
LatestBlockHash []byte
LatestBlockHeight uint
LatestBlockTime int64 // nano
Network string
}{genesisHash, latestBlockHash, latestHeight, latestBlockTime, config.App().GetString("Network")})
}
func NetInfoHandler(w http.ResponseWriter, r *http.Request) {
o, i, _ := p2pSwitch.NumPeers()
numPeers := o + i
listening := p2pSwitch.IsListening()
network := config.App().GetString("Network")
WriteAPIResponse(w, API_OK, struct {
NumPeers int
Listening bool
Network string
}{numPeers, listening, network})
}

View File

@ -0,0 +1,24 @@
{
"Accounts": [
{
"Address": "d7dff9806078899c8da3fe3633cc0bf3c6c2b1bb",
"Amount": 200000000
},
{
"Address": "AC89A6DDF4C309A89A2C4078CE409A5A7B282270",
"Amount": 200000000
}
],
"Validators": [
{
"PubKey": [1, "2239c21c81ea7173a6c489145490c015e05d4b97448933b708a7ec5b7b4921e3"],
"Amount": 1000000,
"UnbondTo": [
{
"Address": "d7dff9806078899c8da3fe3633cc0bf3c6c2b1bb",
"Amount": 100000
}
]
}
]
}

View File

@ -0,0 +1 @@
{"Address":"D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB","PubKey":[1,"2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"],"PrivKey":[1,"FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"],"LastHeight":3,"LastRound":0,"LastStep":2}

150
rpc/test/http_rpc_test.go Normal file
View File

@ -0,0 +1,150 @@
package rpc
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/tendermint/tendermint/binary"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/rpc/core"
"github.com/tendermint/tendermint/types"
"io/ioutil"
"net/http"
"net/url"
"testing"
)
func TestHTTPStatus(t *testing.T) {
resp, err := http.Get(requestAddr + "status")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseStatus
Error string
}
err = json.Unmarshal(body, &status)
if err != nil {
t.Fatal(err)
}
data := status.Data
if data.Network != config.App().GetString("Network") {
t.Fatal(fmt.Errorf("Network mismatch: got %s expected %s", data.Network, config.App().Get("Network")))
}
}
func TestHTTPGenPriv(t *testing.T) {
resp, err := http.Get(requestAddr + "unsafe/gen_priv_account")
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Fatal(resp)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseGenPrivAccount
Error string
}
binary.ReadJSON(&status, body, &err)
if err != nil {
t.Fatal(err)
}
if len(status.Data.PrivAccount.Address) == 0 {
t.Fatal("Failed to generate an address")
}
}
func TestHTTPGetAccount(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
acc := getAccount(t, "HTTP", byteAddr)
if bytes.Compare(acc.Address, byteAddr) != 0 {
t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, byteAddr)
}
}
func TestHTTPSignedTx(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
var byteKey [64]byte
oh, _ := hex.DecodeString(userPriv)
copy(byteKey[:], oh)
amt := uint64(100)
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
tx, priv := signTx(t, "HTTP", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
toAddr = []byte{20, 143, 24, 63, 16, 17, 83, 29, 90, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54}
tx, priv = signTx(t, "HTTP", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
toAddr = []byte{0, 0, 4, 0, 0, 4, 0, 0, 4, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54}
tx, priv = signTx(t, "HTTP", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
}
func TestHTTPBroadcastTx(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
var byteKey [64]byte
oh, _ := hex.DecodeString(userPriv)
copy(byteKey[:], oh)
amt := uint64(100)
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
tx, priv := signTx(t, "HTTP", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
n, w := new(int64), new(bytes.Buffer)
var err error
binary.WriteJSON(tx, w, n, &err)
if err != nil {
t.Fatal(err)
}
b := w.Bytes()
var status struct {
Status string
Data core.ResponseBroadcastTx
Error string
}
requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status)
if status.Status == "ERROR" {
t.Fatal(status.Error)
}
receipt := status.Data.Receipt
if receipt.CreatesContract > 0 {
t.Fatal("This tx does not create a contract")
}
if len(receipt.TxHash) == 0 {
t.Fatal("Failed to compute tx hash")
}
pool := node.MempoolReactor().Mempool
txs := pool.GetProposalTxs()
if len(txs) != mempoolCount+1 {
t.Fatalf("The mem pool has %d txs. Expected %d", len(txs), mempoolCount+1)
}
tx2 := txs[mempoolCount].(*types.SendTx)
mempoolCount += 1
if bytes.Compare(merkle.HashFromBinary(tx), merkle.HashFromBinary(tx2)) != 0 {
t.Fatal("inconsistent hashes for mempool tx and sent tx")
}
}
/*tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx))
err = mint.MempoolReactor.BroadcastTx(tx)
return hex.EncodeToString(merkle.HashFromBinary(tx)), err*/

172
rpc/test/json_rpc_test.go Normal file
View File

@ -0,0 +1,172 @@
package rpc
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/rpc"
"github.com/tendermint/tendermint/rpc/core"
"github.com/tendermint/tendermint/types"
"io/ioutil"
"net/http"
"net/url"
"testing"
)
func TestJSONStatus(t *testing.T) {
s := rpc.JsonRpc{
JsonRpc: "2.0",
Method: "status",
Params: []string{},
Id: 0,
}
b, err := json.Marshal(s)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBuffer(b)
resp, err := http.Post(requestAddr, "text/json", buf)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
status := new(struct {
Status string
Data core.ResponseStatus
Error string
})
err = json.Unmarshal(body, status)
if err != nil {
t.Fatal(err)
}
if status.Data.Network != config.App().GetString("Network") {
t.Fatal(fmt.Errorf("Network mismatch: got %s expected %s", status.Data.Network, config.App().Get("Network")))
}
}
func TestJSONGenPriv(t *testing.T) {
s := rpc.JsonRpc{
JsonRpc: "2.0",
Method: "unsafe/gen_priv_account",
Params: []string{},
Id: 0,
}
b, err := json.Marshal(s)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBuffer(b)
resp, err := http.Post(requestAddr, "text/json", buf)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != 200 {
t.Fatal(resp)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseGenPrivAccount
Error string
}
binary.ReadJSON(&status, body, &err)
if err != nil {
t.Fatal(err)
}
if len(status.Data.PrivAccount.Address) == 0 {
t.Fatal("Failed to generate an address")
}
}
func TestJSONGetAccount(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
acc := getAccount(t, "JSONRPC", byteAddr)
if bytes.Compare(acc.Address, byteAddr) != 0 {
t.Fatalf("Failed to get correct account. Got %x, expected %x", acc.Address, byteAddr)
}
}
func TestJSONSignedTx(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
var byteKey [64]byte
oh, _ := hex.DecodeString(userPriv)
copy(byteKey[:], oh)
amt := uint64(100)
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
tx, priv := signTx(t, "JSONRPC", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
toAddr = []byte{20, 143, 24, 63, 16, 17, 83, 29, 90, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54}
tx, priv = signTx(t, "JSONRPC", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
toAddr = []byte{0, 0, 4, 0, 0, 4, 0, 0, 4, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54}
tx, priv = signTx(t, "JSONRPC", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
}
func TestJSONBroadcastTx(t *testing.T) {
byteAddr, _ := hex.DecodeString(userAddr)
var byteKey [64]byte
oh, _ := hex.DecodeString(userPriv)
copy(byteKey[:], oh)
amt := uint64(100)
toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54}
tx, priv := signTx(t, "JSONRPC", byteAddr, toAddr, byteKey, amt)
checkTx(t, byteAddr, priv, tx)
n, w := new(int64), new(bytes.Buffer)
var err error
binary.WriteJSON(tx, w, n, &err)
if err != nil {
t.Fatal(err)
}
b := w.Bytes()
var status struct {
Status string
Data core.ResponseBroadcastTx
Error string
}
requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status)
if status.Status == "ERROR" {
t.Fatal(status.Error)
}
receipt := status.Data.Receipt
if receipt.CreatesContract > 0 {
t.Fatal("This tx does not create a contract")
}
if len(receipt.TxHash) == 0 {
t.Fatal("Failed to compute tx hash")
}
pool := node.MempoolReactor().Mempool
txs := pool.GetProposalTxs()
if len(txs) != mempoolCount+1 {
t.Fatalf("The mem pool has %d txs. Expected %d", len(txs), mempoolCount+1)
}
tx2 := txs[mempoolCount].(*types.SendTx)
mempoolCount += 1
if bytes.Compare(types.TxId(tx), types.TxId(tx2)) != 0 {
t.Fatal(Fmt("inconsistent hashes for mempool tx and sent tx: %v vs %v", tx, tx2))
}
}
/*tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx))
err = mint.MempoolReactor.BroadcastTx(tx)
return hex.EncodeToString(merkle.HashFromBinary(tx)), err*/

216
rpc/test/test.go Normal file
View File

@ -0,0 +1,216 @@
package rpc
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/daemon"
"github.com/tendermint/tendermint/logger"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/rpc"
"github.com/tendermint/tendermint/rpc/core"
"github.com/tendermint/tendermint/types"
"io/ioutil"
"net/http"
"net/url"
"testing"
)
var (
rpcAddr = "127.0.0.1:8089"
requestAddr = "http://" + rpcAddr + "/"
chainId string
node *daemon.Node
mempoolCount = 0
userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB"
userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"
userPub = "2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3"
)
func newNode(ready chan struct{}) {
// Create & start node
node = daemon.NewNode()
l := p2p.NewDefaultListener("tcp", config.App().GetString("ListenAddr"), false)
node.AddListener(l)
node.Start()
// Run the RPC server.
node.StartRpc()
ready <- struct{}{}
// Sleep forever
ch := make(chan struct{})
<-ch
}
func init() {
rootDir := ".tendermint"
config.Init(rootDir)
app := config.App()
app.Set("SeedNode", "")
app.Set("DB.Backend", "memdb")
app.Set("RPC.HTTP.ListenAddr", rpcAddr)
app.Set("GenesisFile", rootDir+"/genesis.json")
app.Set("PrivValidatorFile", rootDir+"/priv_validator.json")
app.Set("Log.Stdout.Level", "debug")
config.SetApp(app)
logger.InitLog()
// start a node
ready := make(chan struct{})
go newNode(ready)
<-ready
}
func getAccount(t *testing.T, typ string, addr []byte) *account.Account {
var resp *http.Response
var err error
switch typ {
case "JSONRPC":
s := rpc.JsonRpc{
JsonRpc: "2.0",
Method: "get_account",
Params: []string{"0x" + hex.EncodeToString(addr)},
Id: 0,
}
b, err := json.Marshal(s)
if err != nil {
t.Fatal(err)
}
buf := bytes.NewBuffer(b)
resp, err = http.Post(requestAddr, "text/json", buf)
case "HTTP":
resp, err = http.PostForm(requestAddr+"get_account",
url.Values{"address": {string(addr)}})
}
fmt.Println("RESPONSE:", resp)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseGetAccount
Error string
}
fmt.Println(string(body))
binary.ReadJSON(&status, body, &err)
if err != nil {
t.Fatal(err)
}
return status.Data.Account
}
func makeTx(t *testing.T, typ string, from, to []byte, amt uint64) *types.SendTx {
acc := getAccount(t, typ, from)
nonce := 0
if acc != nil {
nonce = int(acc.Sequence) + 1
}
bytePub, err := hex.DecodeString(userPub)
if err != nil {
t.Fatal(err)
}
tx := &types.SendTx{
Inputs: []*types.TxInput{
&types.TxInput{
Address: from,
Amount: amt,
Sequence: uint(nonce),
Signature: account.SignatureEd25519{},
PubKey: account.PubKeyEd25519(bytePub),
},
},
Outputs: []*types.TxOutput{
&types.TxOutput{
Address: to,
Amount: amt,
},
},
}
return tx
}
func requestResponse(t *testing.T, method string, values url.Values, status interface{}) {
resp, err := http.PostForm(requestAddr+method, values)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
fmt.Println(string(body))
binary.ReadJSON(status, body, &err)
if err != nil {
t.Fatal(err)
}
}
func signTx(t *testing.T, typ string, fromAddr, toAddr []byte, key [64]byte, amt uint64) (*types.SendTx, *account.PrivAccount) {
tx := makeTx(t, typ, fromAddr, toAddr, amt)
n, w := new(int64), new(bytes.Buffer)
var err error
binary.WriteJSON(tx, w, n, &err)
if err != nil {
t.Fatal(err)
}
b := w.Bytes()
privAcc := account.GenPrivAccountFromKey(key)
if bytes.Compare(privAcc.PubKey.Address(), fromAddr) != 0 {
t.Fatal("Faield to generate correct priv acc")
}
w = new(bytes.Buffer)
binary.WriteJSON([]*account.PrivAccount{privAcc}, w, n, &err)
if err != nil {
t.Fatal(err)
}
var status struct {
Status string
Data core.ResponseSignTx
Error string
}
requestResponse(t, "unsafe/sign_tx", url.Values{"tx": {string(b)}, "privAccounts": {string(w.Bytes())}}, &status)
if status.Status == "ERROR" {
t.Fatal(status.Error)
}
response := status.Data
tx = response.Tx.(*types.SendTx)
return tx, privAcc
}
func checkTx(t *testing.T, fromAddr []byte, priv *account.PrivAccount, tx *types.SendTx) {
if bytes.Compare(tx.Inputs[0].Address, fromAddr) != 0 {
t.Fatal("Tx input addresses don't match!")
}
signBytes := account.SignBytes(tx)
in := tx.Inputs[0] //(*types.SendTx).Inputs[0]
if err := in.ValidateBasic(); err != nil {
t.Fatal(err)
}
fmt.Println(priv.PubKey, in.PubKey)
// Check signatures
// acc := getAccount(t, byteAddr)
// NOTE: using the acc here instead of the in fails; its PubKeyNil ... ?
if !in.PubKey.VerifyBytes(signBytes, in.Signature) {
t.Fatal(types.ErrTxInvalidSignature)
}
}

221
state/block_cache.go Normal file
View File

@ -0,0 +1,221 @@
package state
import (
"bytes"
"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]storageInfo
}
func NewBlockCache(backend *State) *BlockCache {
return &BlockCache{
db: backend.DB,
backend: backend,
accounts: make(map[string]accountInfo),
storages: make(map[Tuple256]storageInfo),
}
}
func (cache *BlockCache) State() *State {
return cache.backend
}
//-------------------------------------
// BlockCache.account
func (cache *BlockCache) GetAccount(addr []byte) *ac.Account {
acc, _, removed, _ := cache.accounts[string(addr)].unpack()
if removed {
return nil
} else if acc != nil {
return acc
} else {
acc = cache.backend.GetAccount(addr)
cache.accounts[string(addr)] = accountInfo{acc, nil, false, false}
return acc
}
}
func (cache *BlockCache) UpdateAccount(acc *ac.Account) {
addr := acc.Address
// SANITY CHECK
_, storage, removed, _ := cache.accounts[string(addr)].unpack()
if removed {
panic("UpdateAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[string(addr)] = accountInfo{acc, storage, false, true}
}
func (cache *BlockCache) RemoveAccount(addr []byte) {
// SANITY CHECK
_, _, removed, _ := cache.accounts[string(addr)].unpack()
if removed {
panic("RemoveAccount on a removed account")
}
// SANITY CHECK END
cache.accounts[string(addr)] = accountInfo{nil, nil, true, false}
}
// BlockCache.account
//-------------------------------------
// BlockCache.storage
func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) {
// Check cache
info, ok := cache.storages[Tuple256{addr, key}]
if ok {
return info.value
}
// Get or load storage
acc, storage, removed, dirty := cache.accounts[string(addr.Prefix(20))].unpack()
if removed {
panic("GetStorage() on removed account")
}
if storage == nil {
storage = makeStorage(cache.db, acc.StorageRoot)
cache.accounts[string(addr.Prefix(20))] = accountInfo{acc, storage, false, dirty}
}
// Load and set cache
_, val_ := storage.Get(key.Bytes())
value = Zero256
if val_ != nil {
value = RightPadWord256(val_.([]byte))
}
cache.storages[Tuple256{addr, key}] = storageInfo{value, false}
return value
}
// NOTE: Set value to zero to removed from the trie.
func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) {
_, _, removed, _ := cache.accounts[string(addr.Prefix(20))].unpack()
if removed {
panic("SetStorage() on a removed account")
}
cache.storages[Tuple256{addr, key}] = storageInfo{value, true}
}
// 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, _ := cache.accounts[string(addr.Prefix(20))].unpack()
curAddr = addr
curAcc = acc
curAccRemoved = removed
curStorage = storage
}
if curAccRemoved {
continue
}
value, dirty := cache.storages[storageKey].unpack()
if !dirty {
continue
}
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, dirty := cache.accounts[addrStr].unpack()
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))
}
if storage != nil {
newStorageRoot := storage.Save()
if !bytes.Equal(newStorageRoot, acc.StorageRoot) {
acc.StorageRoot = newStorageRoot
dirty = true
}
}
if dirty {
cache.backend.UpdateAccount(acc)
}
}
}
}
//-----------------------------------------------------------------------------
type accountInfo struct {
account *ac.Account
storage merkle.Tree
removed bool
dirty bool
}
func (accInfo accountInfo) unpack() (*ac.Account, merkle.Tree, bool, bool) {
return accInfo.account, accInfo.storage, accInfo.removed, accInfo.dirty
}
type storageInfo struct {
value Word256
dirty bool
}
func (stjInfo storageInfo) unpack() (Word256, bool) {
return stjInfo.value, stjInfo.dirty
}

18
state/common.go Normal file
View File

@ -0,0 +1,18 @@
package state
import (
ac "github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm"
)
type AccountGetter interface {
GetAccount(addr []byte) *ac.Account
}
type VMAccountState interface {
GetAccount(addr Word256) *vm.Account
UpdateAccount(acc *vm.Account)
RemoveAccount(acc *vm.Account)
CreateAccount(creator *vm.Account) *vm.Account
}

593
state/execution.go Normal file
View File

@ -0,0 +1,593 @@
package state
import (
"bytes"
"errors"
"github.com/tendermint/tendermint/account"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/vm"
)
// NOTE: If an error occurs during block execution, state will be left
// at an invalid state. Copy the state before calling ExecBlock!
func ExecBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
err := execBlock(s, block, blockPartsHeader)
if err != nil {
return err
}
// State.Hash should match block.StateHash
stateHash := s.Hash()
if !bytes.Equal(stateHash, block.StateHash) {
return Errorf("Invalid state hash. Expected %X, got %X",
stateHash, block.StateHash)
}
return nil
}
// executes transactions of a block, does not check block.StateHash
// NOTE: If an error occurs during block execution, state will be left
// at an invalid state. Copy the state before calling execBlock!
func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeader) error {
// Basic block validation.
err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime)
if err != nil {
return err
}
// Validate block Validation.
if block.Height == 1 {
if len(block.Validation.Commits) != 0 {
return errors.New("Block at height 1 (first block) should have no Validation commits")
}
} else {
if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() {
return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
s.LastBondedValidators.Size(), len(block.Validation.Commits)))
}
var sumVotingPower uint64
s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool {
commit := block.Validation.Commits[index]
if commit.IsZero() {
return false
} else {
vote := &types.Vote{
Height: block.Height - 1,
Round: commit.Round,
Type: types.VoteTypeCommit,
BlockHash: block.LastBlockHash,
BlockParts: block.LastBlockParts,
}
if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) {
sumVotingPower += val.VotingPower
return false
} else {
log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote))
err = errors.New("Invalid validation signature")
return true
}
}
})
if err != nil {
return err
}
if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 {
return errors.New("Insufficient validation voting power")
}
}
// Update Validator.LastCommitHeight as necessary.
for i, commit := range block.Validation.Commits {
if commit.IsZero() {
continue
}
_, val := s.LastBondedValidators.GetByIndex(uint(i))
if val == nil {
panic(Fmt("Failed to fetch validator at index %v", i))
}
if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.BondedValidators.Update(val_)
if !updated {
panic("Failed to update bonded validator LastCommitHeight")
}
} else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.UnbondingValidators.Update(val_)
if !updated {
panic("Failed to update unbonding validator LastCommitHeight")
}
} else {
panic("Could not find validator")
}
}
// Remember LastBondedValidators
s.LastBondedValidators = s.BondedValidators.Copy()
// Create BlockCache to cache changes to state.
blockCache := NewBlockCache(s)
// Commit each tx
for _, tx := range block.Data.Txs {
err := ExecTx(blockCache, tx, true)
if err != nil {
return InvalidTxError{tx, err}
}
}
// Now sync the BlockCache to the backend.
blockCache.Sync()
// If any unbonding periods are over,
// reward account with bonded coins.
toRelease := []*Validator{}
s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool {
if val.UnbondHeight+unbondingPeriodBlocks < block.Height {
toRelease = append(toRelease, val)
}
return false
})
for _, val := range toRelease {
s.releaseValidator(val)
}
// If any validators haven't signed in a while,
// unbond them, they have timed out.
toTimeout := []*Validator{}
s.BondedValidators.Iterate(func(index uint, val *Validator) bool {
lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight)
if lastActivityHeight+validatorTimeoutBlocks < block.Height {
log.Info("Validator timeout", "validator", val, "height", block.Height)
toTimeout = append(toTimeout, val)
}
return false
})
for _, val := range toTimeout {
s.unbondValidator(val)
}
// Increment validator AccumPowers
s.BondedValidators.IncrementAccum(1)
s.LastBlockHeight = block.Height
s.LastBlockHash = block.Hash()
s.LastBlockParts = blockPartsHeader
s.LastBlockTime = block.Time
return nil
}
// The accounts from the TxInputs must either already have
// account.PubKey.(type) != PubKeyNil, (it must be known),
// or it must be specified in the TxInput. If redeclared,
// the TxInput is modified and input.PubKey set to PubKeyNil.
func getOrMakeAccounts(state AccountGetter, ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) {
accounts := map[string]*account.Account{}
for _, in := range ins {
// Account shouldn't be duplicated
if _, ok := accounts[string(in.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := state.GetAccount(in.Address)
if acc == nil {
return nil, types.ErrTxInvalidAddress
}
// PubKey should be present in either "account" or "in"
if err := checkInputPubKey(acc, in); err != nil {
return nil, err
}
accounts[string(in.Address)] = acc
}
for _, out := range outs {
// Account shouldn't be duplicated
if _, ok := accounts[string(out.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := state.GetAccount(out.Address)
// output account may be nil (new)
if acc == nil {
acc = &account.Account{
Address: out.Address,
PubKey: account.PubKeyNil{},
Sequence: 0,
Balance: 0,
}
}
accounts[string(out.Address)] = acc
}
return accounts, nil
}
func checkInputPubKey(acc *account.Account, in *types.TxInput) error {
if _, isNil := acc.PubKey.(account.PubKeyNil); isNil {
if _, isNil := in.PubKey.(account.PubKeyNil); isNil {
return types.ErrTxUnknownPubKey
}
if !bytes.Equal(in.PubKey.Address(), acc.Address) {
return types.ErrTxInvalidPubKey
}
acc.PubKey = in.PubKey
} else {
in.PubKey = account.PubKeyNil{}
}
return nil
}
func validateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total uint64, err error) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("validateInputs() expects account in accounts")
}
err = validateInput(acc, signBytes, in)
if err != nil {
return
}
// Good. Add amount to total
total += in.Amount
}
return total, nil
}
func validateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (err error) {
// Check TxInput basic
if err := in.ValidateBasic(); err != nil {
return err
}
// Check signatures
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
return types.ErrTxInvalidSignature
}
// Check sequences
if acc.Sequence+1 != in.Sequence {
return types.ErrTxInvalidSequence{
Got: uint64(in.Sequence),
Expected: uint64(acc.Sequence + 1),
}
}
// Check amount
if acc.Balance < in.Amount {
return types.ErrTxInsufficientFunds
}
return nil
}
func validateOutputs(outs []*types.TxOutput) (total uint64, err error) {
for _, out := range outs {
// Check TxOutput basic
if err := out.ValidateBasic(); err != nil {
return 0, err
}
// Good. Add amount to total
total += out.Amount
}
return total, nil
}
func adjustByInputs(accounts map[string]*account.Account, ins []*types.TxInput) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("adjustByInputs() expects account in accounts")
}
if acc.Balance < in.Amount {
panic("adjustByInputs() expects sufficient funds")
}
acc.Balance -= in.Amount
acc.Sequence += 1
}
}
func adjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutput) {
for _, out := range outs {
acc := accounts[string(out.Address)]
if acc == nil {
panic("adjustByOutputs() expects account in accounts")
}
acc.Balance += out.Amount
}
}
// If the tx is invalid, an error will be returned.
// Unlike ExecBlock(), state will not be altered.
func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool) error {
// TODO: do something with fees
fees := uint64(0)
_s := blockCache.State() // hack to access validators.
// Exec tx
switch tx := tx_.(type) {
case *types.SendTx:
accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, tx.Outputs)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
outTotal, err := validateOutputs(tx.Outputs)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
adjustByInputs(accounts, tx.Inputs)
adjustByOutputs(accounts, tx.Outputs)
for _, acc := range accounts {
blockCache.UpdateAccount(acc)
}
return nil
case *types.CallTx:
var inAcc, outAcc *account.Account
// Validate input
inAcc = blockCache.GetAccount(tx.Input.Address)
if inAcc == nil {
log.Debug(Fmt("Can't find in account %X", tx.Input.Address))
return types.ErrTxInvalidAddress
}
// pubKey should be present in either "inAcc" or "tx.Input"
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address))
return err
}
signBytes := account.SignBytes(tx)
err := validateInput(inAcc, signBytes, tx.Input)
if err != nil {
log.Debug(Fmt("validateInput failed on %X:", tx.Input.Address))
return err
}
if tx.Input.Amount < tx.Fee {
log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
return types.ErrTxInsufficientFunds
}
createAccount := len(tx.Address) == 0
if !createAccount {
// Validate output
if len(tx.Address) != 20 {
log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address))
return types.ErrTxInvalidAddress
}
// this may be nil if we are still in mempool and contract was created in same block as this tx
// but that's fine, because the account will be created properly when the create tx runs in the block
// and then this won't return nil. otherwise, we take their fee
outAcc = blockCache.GetAccount(tx.Address)
}
log.Debug(Fmt("Out account: %v", outAcc))
// Good!
value := tx.Input.Amount - tx.Fee
inAcc.Sequence += 1
if runCall {
var (
gas uint64 = tx.GasLimit
err error = nil
caller *vm.Account = toVMAccount(inAcc)
callee *vm.Account = nil
code []byte = nil
txCache = NewTxCache(blockCache)
params = vm.Params{
BlockHeight: uint64(_s.LastBlockHeight),
BlockHash: RightPadWord256(_s.LastBlockHash),
BlockTime: _s.LastBlockTime.Unix(),
GasLimit: 10000000,
}
)
// Maybe create a new callee account if
// this transaction is creating a new contract.
if !createAccount {
if outAcc == nil {
// take fees (sorry pal)
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
return types.ErrTxInvalidAddress
}
callee = toVMAccount(outAcc)
code = callee.Code
log.Debug(Fmt("Calling contract %X with code %X", callee.Address, callee.Code))
} else {
callee = txCache.CreateAccount(caller)
log.Debug(Fmt("Created new account %X", callee.Address))
code = tx.Data
}
log.Debug(Fmt("Code for this contract: %X", code))
txCache.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe.
txCache.UpdateAccount(callee) // because we adjusted by input above.
vmach := vm.NewVM(txCache, params, caller.Address)
// NOTE: Call() transfers the value from caller to callee iff call succeeds.
ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas)
if err != nil {
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
log.Debug(Fmt("Error on execution: %v", err))
inAcc.Balance -= tx.Fee
blockCache.UpdateAccount(inAcc)
// Throw away 'txCache' which holds incomplete updates (don't sync it).
} else {
log.Debug("Successful execution")
// Success
if createAccount {
callee.Code = ret
}
txCache.Sync()
}
// Create a receipt from the ret and whether errored.
log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
} else {
// The mempool does not call txs until
// the proposer determines the order of txs.
// So mempool will skip the actual .Call(),
// and only deduct from the caller's balance.
inAcc.Balance -= value
if createAccount {
inAcc.Sequence += 1
}
blockCache.UpdateAccount(inAcc)
}
return nil
case *types.BondTx:
valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address())
if valInfo != nil {
// TODO: In the future, check that the validator wasn't destroyed,
// add funds, merge UnbondTo outputs, and unbond validator.
return errors.New("Adding coins to existing validators not yet supported")
}
accounts, err := getOrMakeAccounts(blockCache, tx.Inputs, nil)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := validateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
if err := tx.PubKey.ValidateBasic(); err != nil {
return err
}
outTotal, err := validateOutputs(tx.UnbondTo)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
adjustByInputs(accounts, tx.Inputs)
for _, acc := range accounts {
blockCache.UpdateAccount(acc)
}
// Add ValidatorInfo
_s.SetValidatorInfo(&ValidatorInfo{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
UnbondTo: tx.UnbondTo,
FirstBondHeight: _s.LastBlockHeight + 1,
FirstBondAmount: outTotal,
})
// Add Validator
added := _s.BondedValidators.Add(&Validator{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
BondHeight: _s.LastBlockHeight + 1,
VotingPower: outTotal,
Accum: 0,
})
if !added {
panic("Failed to add validator")
}
return nil
case *types.UnbondTx:
// The validator must be active
_, val := _s.BondedValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be greater than val.LastCommitHeight
if tx.Height <= val.LastCommitHeight {
return errors.New("Invalid unbond height")
}
// Good!
_s.unbondValidator(val)
return nil
case *types.RebondTx:
// The validator must be inactive
_, val := _s.UnbondingValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be equal to the next height
if tx.Height != _s.LastBlockHeight+1 {
return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", _s.LastBlockHeight+1, tx.Height))
}
// Good!
_s.rebondValidator(val)
return nil
case *types.DupeoutTx:
// Verify the signatures
_, accused := _s.BondedValidators.GetByAddress(tx.Address)
if accused == nil {
_, accused = _s.UnbondingValidators.GetByAddress(tx.Address)
if accused == nil {
return types.ErrTxInvalidAddress
}
}
voteASignBytes := account.SignBytes(&tx.VoteA)
voteBSignBytes := account.SignBytes(&tx.VoteB)
if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) ||
!accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) {
return types.ErrTxInvalidSignature
}
// Verify equivocation
// TODO: in the future, just require one vote from a previous height that
// doesn't exist on this chain.
if tx.VoteA.Height != tx.VoteB.Height {
return errors.New("DupeoutTx heights don't match")
}
if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round {
// Check special case (not an error, validator must be slashed!)
// Validators should not sign another vote after committing.
} else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round {
// We need to check both orderings of the votes
} else {
if tx.VoteA.Round != tx.VoteB.Round {
return errors.New("DupeoutTx rounds don't match")
}
if tx.VoteA.Type != tx.VoteB.Type {
return errors.New("DupeoutTx types don't match")
}
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
return errors.New("DupeoutTx blockhashes shouldn't match")
}
}
// Good! (Bad validator!)
_s.destroyValidator(accused)
return nil
default:
panic("Unknown Tx type")
}
}

View File

@ -2,17 +2,14 @@ package state
import (
"bytes"
"errors"
"fmt"
"time"
"github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
dbm "github.com/tendermint/tendermint/db"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/vm"
)
var (
@ -25,17 +22,6 @@ var (
//-----------------------------------------------------------------------------
type InvalidTxError struct {
Tx types.Tx
Reason error
}
func (txErr InvalidTxError) Error() string {
return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
}
//-----------------------------------------------------------------------------
// NOTE: not goroutine-safe.
type State struct {
DB dbm.DB
@ -78,7 +64,6 @@ func LoadState(db dbm.DB) *State {
return s
}
// Save this state into the db.
func (s *State) Save() {
s.accounts.Save()
s.validatorInfos.Save()
@ -98,6 +83,9 @@ func (s *State) Save() {
s.DB.Set(stateKey, buf.Bytes())
}
// CONTRACT:
// Copy() is a cheap way to take a snapshot,
// as if State were copied by value.
func (s *State) Copy() *State {
return &State{
DB: s.DB,
@ -113,437 +101,81 @@ func (s *State) Copy() *State {
}
}
// The accounts from the TxInputs must either already have
// account.PubKey.(type) != PubKeyNil, (it must be known),
// or it must be specified in the TxInput. If redeclared,
// the TxInput is modified and input.PubKey set to PubKeyNil.
func (s *State) GetOrMakeAccounts(ins []*types.TxInput, outs []*types.TxOutput) (map[string]*account.Account, error) {
accounts := map[string]*account.Account{}
for _, in := range ins {
// Account shouldn't be duplicated
if _, ok := accounts[string(in.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := s.GetAccount(in.Address)
if acc == nil {
return nil, types.ErrTxInvalidAddress
}
// PubKey should be present in either "account" or "in"
if err := checkInputPubKey(acc, in); err != nil {
return nil, err
}
accounts[string(in.Address)] = acc
// Returns a hash that represents the state data, excluding Last*
func (s *State) Hash() []byte {
hashables := []merkle.Hashable{
s.BondedValidators,
s.UnbondingValidators,
s.accounts,
s.validatorInfos,
}
for _, out := range outs {
// Account shouldn't be duplicated
if _, ok := accounts[string(out.Address)]; ok {
return nil, types.ErrTxDuplicateAddress
}
acc := s.GetAccount(out.Address)
// output account may be nil (new)
if acc == nil {
acc = &account.Account{
Address: out.Address,
PubKey: account.PubKeyNil{},
Sequence: 0,
Balance: 0,
}
}
accounts[string(out.Address)] = acc
}
return accounts, nil
return merkle.HashFromHashables(hashables)
}
func checkInputPubKey(acc *account.Account, in *types.TxInput) error {
if _, isNil := acc.PubKey.(account.PubKeyNil); isNil {
if _, isNil := in.PubKey.(account.PubKeyNil); isNil {
return types.ErrTxUnknownPubKey
}
if !bytes.Equal(in.PubKey.Address(), acc.Address) {
return types.ErrTxInvalidPubKey
}
acc.PubKey = in.PubKey
} else {
in.PubKey = account.PubKeyNil{}
}
return nil
}
func (s *State) ValidateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total uint64, err error) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("ValidateInputs() expects account in accounts")
}
err = s.ValidateInput(acc, signBytes, in)
if err != nil {
return
}
// Good. Add amount to total
total += in.Amount
}
return total, nil
}
func (s *State) ValidateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (err error) {
// Check TxInput basic
if err := in.ValidateBasic(); err != nil {
// Mutates the block in place and updates it with new state hash.
func (s *State) SetBlockStateHash(block *types.Block) error {
sCopy := s.Copy()
err := execBlock(sCopy, block, types.PartSetHeader{})
if err != nil {
return err
}
// Check signatures
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
return types.ErrTxInvalidSignature
}
// Check sequences
if acc.Sequence+1 != in.Sequence {
return types.ErrTxInvalidSequence{
Got: uint64(in.Sequence),
Expected: uint64(acc.Sequence + 1),
}
}
// Check amount
if acc.Balance < in.Amount {
return types.ErrTxInsufficientFunds
}
// Set block.StateHash
block.StateHash = sCopy.Hash()
return nil
}
func (s *State) ValidateOutputs(outs []*types.TxOutput) (total uint64, err error) {
for _, out := range outs {
// Check TxOutput basic
if err := out.ValidateBasic(); err != nil {
return 0, err
}
// Good. Add amount to total
total += out.Amount
//-------------------------------------
// State.accounts
// The returned Account is a copy, so mutating it
// has no side effects.
// Implements Statelike
func (s *State) GetAccount(address []byte) *account.Account {
_, acc := s.accounts.Get(address)
if acc == nil {
return nil
}
return total, nil
return acc.(*account.Account).Copy()
}
func (s *State) AdjustByInputs(accounts map[string]*account.Account, ins []*types.TxInput) {
for _, in := range ins {
acc := accounts[string(in.Address)]
if acc == nil {
panic("AdjustByInputs() expects account in accounts")
}
if acc.Balance < in.Amount {
panic("AdjustByInputs() expects sufficient funds")
}
acc.Balance -= in.Amount
acc.Sequence += 1
}
// The account is copied before setting, so mutating it
// afterwards has no side effects.
// Implements Statelike
func (s *State) UpdateAccount(account *account.Account) bool {
return s.accounts.Set(account.Address, account.Copy())
}
func (s *State) AdjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutput) {
for _, out := range outs {
acc := accounts[string(out.Address)]
if acc == nil {
panic("AdjustByOutputs() expects account in accounts")
}
acc.Balance += out.Amount
}
// Implements Statelike
func (s *State) RemoveAccount(address []byte) bool {
_, removed := s.accounts.Remove(address)
return removed
}
// If the tx is invalid, an error will be returned.
// Unlike AppendBlock(), state will not be altered.
func (s *State) ExecTx(tx_ types.Tx, runCall bool) error {
// The returned Account is a copy, so mutating it
// has no side effects.
func (s *State) GetAccounts() merkle.Tree {
return s.accounts.Copy()
}
// TODO: do something with fees
fees := uint64(0)
// State.accounts
//-------------------------------------
// State.validators
// Exec tx
switch tx := tx_.(type) {
case *types.SendTx:
accounts, err := s.GetOrMakeAccounts(tx.Inputs, tx.Outputs)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
outTotal, err := s.ValidateOutputs(tx.Outputs)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
s.AdjustByInputs(accounts, tx.Inputs)
s.AdjustByOutputs(accounts, tx.Outputs)
s.UpdateAccounts(accounts)
// The returned ValidatorInfo is a copy, so mutating it
// has no side effects.
func (s *State) GetValidatorInfo(address []byte) *ValidatorInfo {
_, valInfo := s.validatorInfos.Get(address)
if valInfo == nil {
return nil
case *types.CallTx:
var inAcc, outAcc *account.Account
// Validate input
inAcc = s.GetAccount(tx.Input.Address)
if inAcc == nil {
log.Debug(Fmt("Can't find in account %X", tx.Input.Address))
return types.ErrTxInvalidAddress
}
// pubKey should be present in either "inAcc" or "tx.Input"
if err := checkInputPubKey(inAcc, tx.Input); err != nil {
log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address))
return err
}
signBytes := account.SignBytes(tx)
err := s.ValidateInput(inAcc, signBytes, tx.Input)
if err != nil {
log.Debug(Fmt("ValidateInput failed on %X:", tx.Input.Address))
return err
}
if tx.Input.Amount < tx.Fee {
log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
return types.ErrTxInsufficientFunds
}
createAccount := len(tx.Address) == 0
if !createAccount {
// Validate output
if len(tx.Address) != 20 {
log.Debug(Fmt("Destination address is not 20 bytes %X", tx.Address))
return types.ErrTxInvalidAddress
}
// this may be nil if we are still in mempool and contract was created in same block as this tx
// but that's fine, because the account will be created properly when the create tx runs in the block
// and then this won't return nil. otherwise, we take their fee
outAcc = s.GetAccount(tx.Address)
}
log.Debug(Fmt("Out account: %v", outAcc))
// Good!
value := tx.Input.Amount - tx.Fee
inAcc.Sequence += 1
if runCall {
var (
gas uint64 = tx.GasLimit
err error = nil
caller *vm.Account = toVMAccount(inAcc)
callee *vm.Account = nil
code []byte = nil
appState = NewVMAppState(s) // TODO: confusing.
params = vm.Params{
BlockHeight: uint64(s.LastBlockHeight),
BlockHash: vm.BytesToWord(s.LastBlockHash),
BlockTime: s.LastBlockTime.Unix(),
GasLimit: 10000000,
}
)
// Maybe create a new callee account if
// this transaction is creating a new contract.
if !createAccount {
if outAcc == nil {
// take fees (sorry pal)
inAcc.Balance -= tx.Fee
s.UpdateAccount(inAcc)
log.Debug(Fmt("Cannot find destination address %X. Deducting fee from caller", tx.Address))
return types.ErrTxInvalidAddress
}
callee = toVMAccount(outAcc)
code = callee.Code
log.Debug(Fmt("Calling contract %X with code %X", callee.Address.Address(), callee.Code))
} else {
callee, err = appState.CreateAccount(caller)
if err != nil {
log.Debug(Fmt("Error creating account"))
return err
}
log.Debug(Fmt("Created new account %X", callee.Address.Address()))
code = tx.Data
}
log.Debug(Fmt("Code for this contract: %X", code))
appState.UpdateAccount(caller) // because we adjusted by input above, and bumped nonce maybe.
appState.UpdateAccount(callee) // because we adjusted by input above.
vmach := vm.NewVM(appState, params, caller.Address)
// NOTE: Call() transfers the value from caller to callee iff call succeeds.
ret, err := vmach.Call(caller, callee, code, tx.Data, value, &gas)
if err != nil {
// Failure. Charge the gas fee. The 'value' was otherwise not transferred.
log.Debug(Fmt("Error on execution: %v", err))
inAcc.Balance -= tx.Fee
s.UpdateAccount(inAcc)
// Throw away 'appState' which holds incomplete updates (don't sync it).
} else {
log.Debug("Successful execution")
// Success
if createAccount {
callee.Code = ret
}
appState.Sync()
}
// Create a receipt from the ret and whether errored.
log.Info("VM call complete", "caller", caller, "callee", callee, "return", ret, "err", err)
} else {
// The mempool does not call txs until
// the proposer determines the order of txs.
// So mempool will skip the actual .Call(),
// and only deduct from the caller's balance.
inAcc.Balance -= value
if createAccount {
inAcc.Sequence += 1
}
s.UpdateAccount(inAcc)
}
return nil
case *types.BondTx:
valInfo := s.GetValidatorInfo(tx.PubKey.Address())
if valInfo != nil {
// TODO: In the future, check that the validator wasn't destroyed,
// add funds, merge UnbondTo outputs, and unbond validator.
return errors.New("Adding coins to existing validators not yet supported")
}
accounts, err := s.GetOrMakeAccounts(tx.Inputs, nil)
if err != nil {
return err
}
signBytes := account.SignBytes(tx)
inTotal, err := s.ValidateInputs(accounts, signBytes, tx.Inputs)
if err != nil {
return err
}
if err := tx.PubKey.ValidateBasic(); err != nil {
return err
}
outTotal, err := s.ValidateOutputs(tx.UnbondTo)
if err != nil {
return err
}
if outTotal > inTotal {
return types.ErrTxInsufficientFunds
}
fee := inTotal - outTotal
fees += fee
// Good! Adjust accounts
s.AdjustByInputs(accounts, tx.Inputs)
s.UpdateAccounts(accounts)
// Add ValidatorInfo
s.SetValidatorInfo(&ValidatorInfo{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
UnbondTo: tx.UnbondTo,
FirstBondHeight: s.LastBlockHeight + 1,
FirstBondAmount: outTotal,
})
// Add Validator
added := s.BondedValidators.Add(&Validator{
Address: tx.PubKey.Address(),
PubKey: tx.PubKey,
BondHeight: s.LastBlockHeight + 1,
VotingPower: outTotal,
Accum: 0,
})
if !added {
panic("Failed to add validator")
}
return nil
case *types.UnbondTx:
// The validator must be active
_, val := s.BondedValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be greater than val.LastCommitHeight
if tx.Height <= val.LastCommitHeight {
return errors.New("Invalid unbond height")
}
// Good!
s.unbondValidator(val)
return nil
case *types.RebondTx:
// The validator must be inactive
_, val := s.UnbondingValidators.GetByAddress(tx.Address)
if val == nil {
return types.ErrTxInvalidAddress
}
// Verify the signature
signBytes := account.SignBytes(tx)
if !val.PubKey.VerifyBytes(signBytes, tx.Signature) {
return types.ErrTxInvalidSignature
}
// tx.Height must be equal to the next height
if tx.Height != s.LastBlockHeight+1 {
return errors.New(Fmt("Invalid rebond height. Expected %v, got %v", s.LastBlockHeight+1, tx.Height))
}
// Good!
s.rebondValidator(val)
return nil
case *types.DupeoutTx:
// Verify the signatures
_, accused := s.BondedValidators.GetByAddress(tx.Address)
if accused == nil {
_, accused = s.UnbondingValidators.GetByAddress(tx.Address)
if accused == nil {
return types.ErrTxInvalidAddress
}
}
voteASignBytes := account.SignBytes(&tx.VoteA)
voteBSignBytes := account.SignBytes(&tx.VoteB)
if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) ||
!accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) {
return types.ErrTxInvalidSignature
}
// Verify equivocation
// TODO: in the future, just require one vote from a previous height that
// doesn't exist on this chain.
if tx.VoteA.Height != tx.VoteB.Height {
return errors.New("DupeoutTx heights don't match")
}
if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round {
// Check special case (not an error, validator must be slashed!)
// Validators should not sign another vote after committing.
} else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round {
// We need to check both orderings of the votes
} else {
if tx.VoteA.Round != tx.VoteB.Round {
return errors.New("DupeoutTx rounds don't match")
}
if tx.VoteA.Type != tx.VoteB.Type {
return errors.New("DupeoutTx types don't match")
}
if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) {
return errors.New("DupeoutTx blockhashes shouldn't match")
}
}
// Good! (Bad validator!)
s.destroyValidator(accused)
return nil
default:
panic("Unknown Tx type")
}
return valInfo.(*ValidatorInfo).Copy()
}
// Returns false if new, true if updated.
// The valInfo is copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) {
return s.validatorInfos.Set(valInfo.Address, valInfo.Copy())
}
func (s *State) unbondValidator(val *Validator) {
@ -582,12 +214,14 @@ func (s *State) releaseValidator(val *Validator) {
s.SetValidatorInfo(valInfo)
// Send coins back to UnbondTo outputs
accounts, err := s.GetOrMakeAccounts(nil, valInfo.UnbondTo)
accounts, err := getOrMakeAccounts(s, nil, valInfo.UnbondTo)
if err != nil {
panic("Couldn't get or make unbondTo accounts")
}
s.AdjustByOutputs(accounts, valInfo.UnbondTo)
s.UpdateAccounts(accounts)
adjustByOutputs(accounts, valInfo.UnbondTo)
for _, acc := range accounts {
s.UpdateAccount(acc)
}
// Remove validator from UnbondingValidators
_, removed := s.UnbondingValidators.Remove(val.Address)
@ -617,219 +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
}
func (s *State) SetBlockStateHash(block *types.Block) error {
sCopy := s.Copy()
err := sCopy.appendBlock(block, types.PartSetHeader{})
if err != nil {
return err
}
// Set block.StateHash
block.StateHash = sCopy.Hash()
return nil
// State.storage
//-------------------------------------
//-----------------------------------------------------------------------------
type InvalidTxError struct {
Tx types.Tx
Reason error
}
// Appends the block, does not check block.StateHash
// NOTE: If an error occurs during block execution, state will be left
// at an invalid state. Copy the state before calling appendBlock!
func (s *State) appendBlock(block *types.Block, blockPartsHeader types.PartSetHeader) error {
// Basic block validation.
err := block.ValidateBasic(s.LastBlockHeight, s.LastBlockHash, s.LastBlockParts, s.LastBlockTime)
if err != nil {
return err
}
// Validate block Validation.
if block.Height == 1 {
if len(block.Validation.Commits) != 0 {
return errors.New("Block at height 1 (first block) should have no Validation commits")
}
} else {
if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() {
return errors.New(Fmt("Invalid block validation size. Expected %v, got %v",
s.LastBondedValidators.Size(), len(block.Validation.Commits)))
}
var sumVotingPower uint64
s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool {
commit := block.Validation.Commits[index]
if commit.IsZero() {
return false
} else {
vote := &types.Vote{
Height: block.Height - 1,
Round: commit.Round,
Type: types.VoteTypeCommit,
BlockHash: block.LastBlockHash,
BlockParts: block.LastBlockParts,
}
if val.PubKey.VerifyBytes(account.SignBytes(vote), commit.Signature) {
sumVotingPower += val.VotingPower
return false
} else {
log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote))
err = errors.New("Invalid validation signature")
return true
}
}
})
if err != nil {
return err
}
if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 {
return errors.New("Insufficient validation voting power")
}
}
// Update Validator.LastCommitHeight as necessary.
for i, commit := range block.Validation.Commits {
if commit.IsZero() {
continue
}
_, val := s.LastBondedValidators.GetByIndex(uint(i))
if val == nil {
panic(Fmt("Failed to fetch validator at index %v", i))
}
if _, val_ := s.BondedValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.BondedValidators.Update(val_)
if !updated {
panic("Failed to update bonded validator LastCommitHeight")
}
} else if _, val_ := s.UnbondingValidators.GetByAddress(val.Address); val_ != nil {
val_.LastCommitHeight = block.Height - 1
updated := s.UnbondingValidators.Update(val_)
if !updated {
panic("Failed to update unbonding validator LastCommitHeight")
}
} else {
panic("Could not find validator")
}
}
// Remember LastBondedValidators
s.LastBondedValidators = s.BondedValidators.Copy()
// Commit each tx
for _, tx := range block.Data.Txs {
err := s.ExecTx(tx, true)
if err != nil {
return InvalidTxError{tx, err}
}
}
// If any unbonding periods are over,
// reward account with bonded coins.
toRelease := []*Validator{}
s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool {
if val.UnbondHeight+unbondingPeriodBlocks < block.Height {
toRelease = append(toRelease, val)
}
return false
})
for _, val := range toRelease {
s.releaseValidator(val)
}
// If any validators haven't signed in a while,
// unbond them, they have timed out.
toTimeout := []*Validator{}
s.BondedValidators.Iterate(func(index uint, val *Validator) bool {
lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight)
if lastActivityHeight+validatorTimeoutBlocks < block.Height {
log.Info("Validator timeout", "validator", val, "height", block.Height)
toTimeout = append(toTimeout, val)
}
return false
})
for _, val := range toTimeout {
s.unbondValidator(val)
}
// Increment validator AccumPowers
s.BondedValidators.IncrementAccum(1)
s.LastBlockHeight = block.Height
s.LastBlockHash = block.Hash()
s.LastBlockParts = blockPartsHeader
s.LastBlockTime = block.Time
return nil
}
// The returned Account is a copy, so mutating it
// has no side effects.
func (s *State) GetAccount(address []byte) *account.Account {
_, acc := s.accounts.Get(address)
if acc == nil {
return nil
}
return acc.(*account.Account).Copy()
}
// The returned Account is a copy, so mutating it
// has no side effects.
func (s *State) GetAccounts() merkle.Tree {
return s.accounts.Copy()
}
// The account is copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) UpdateAccount(account *account.Account) {
s.accounts.Set(account.Address, account.Copy())
}
// The accounts are copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) UpdateAccounts(accounts map[string]*account.Account) {
for _, acc := range accounts {
s.accounts.Set(acc.Address, acc.Copy())
}
}
func (s *State) RemoveAccount(address []byte) bool {
_, removed := s.accounts.Remove(address)
return removed
}
// The returned ValidatorInfo is a copy, so mutating it
// has no side effects.
func (s *State) GetValidatorInfo(address []byte) *ValidatorInfo {
_, valInfo := s.validatorInfos.Get(address)
if valInfo == nil {
return nil
}
return valInfo.(*ValidatorInfo).Copy()
}
// Returns false if new, true if updated.
// The valInfo is copied before setting, so mutating it
// afterwards has no side effects.
func (s *State) SetValidatorInfo(valInfo *ValidatorInfo) (updated bool) {
return s.validatorInfos.Set(valInfo.Address, valInfo.Copy())
}
// Returns a hash that represents the state data,
// excluding Last*
func (s *State) Hash() []byte {
hashables := []merkle.Hashable{
s.BondedValidators,
s.UnbondingValidators,
s.accounts,
s.validatorInfos,
}
return merkle.HashFromHashables(hashables)
func (txErr InvalidTxError) Error() string {
return fmt.Sprintf("Invalid tx: [%v] reason: [%v]", txErr.Tx, txErr.Reason)
}

View File

@ -10,6 +10,17 @@ import (
"time"
)
func execTxWithState(state *State, tx types.Tx, runCall bool) error {
cache := NewBlockCache(state)
err := ExecTx(cache, tx, runCall)
if err != nil {
return err
} else {
cache.Sync()
return nil
}
}
func TestCopyState(t *testing.T) {
// Generate a random state
s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000)
@ -93,7 +104,7 @@ func TestGenesisSaveLoad(t *testing.T) {
blockParts := block.MakePartSet()
// Now append the block to s0.
err := s0.AppendBlock(block, blockParts.Header())
err := ExecBlock(s0, block, blockParts.Header())
if err != nil {
t.Error("Error appending initial block:", err)
}
@ -182,7 +193,7 @@ func TestTxSequence(t *testing.T) {
tx := makeSendTx(sequence)
tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
stateCopy := state.Copy()
err := stateCopy.ExecTx(tx, true)
err := execTxWithState(stateCopy, tx, true)
if i == 1 {
// Sequence is good.
if err != nil {
@ -241,7 +252,7 @@ func TestTxs(t *testing.T) {
}
tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
err := state.ExecTx(tx, true)
err := execTxWithState(state, tx, true)
if err != nil {
t.Errorf("Got error in executing send transaction, %v", err)
}
@ -278,7 +289,7 @@ func TestTxs(t *testing.T) {
},
}
tx.Inputs[0].Signature = privAccounts[0].Sign(tx)
err := state.ExecTx(tx, true)
err := execTxWithState(state, tx, true)
if err != nil {
t.Errorf("Got error in executing bond transaction, %v", err)
}
@ -345,7 +356,7 @@ func TestAddValidator(t *testing.T) {
}
// Now append the block to s0.
err := s0.AppendBlock(block0, block0Parts.Header())
err := ExecBlock(s0, block0, block0Parts.Header())
if err != nil {
t.Error("Error appending initial block:", err)
}
@ -379,7 +390,7 @@ func TestAddValidator(t *testing.T) {
}, nil,
)
block1Parts := block1.MakePartSet()
err = s0.AppendBlock(block1, block1Parts.Header())
err = ExecBlock(s0, block1, block1Parts.Header())
if err != nil {
t.Error("Error appending secondary block:", err)
}

191
state/tx_cache.go Normal file
View File

@ -0,0 +1,191 @@
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 from backend
return cache.backend.GetStorage(addr, key)
}
// NOTE: Set value to zero to removed from the trie.
func (cache *TxCache) SetStorage(addr Word256, key Word256, value Word256) {
_, removed := vmUnpack(cache.accounts[addr])
if removed {
panic("SetStorage() on a removed account")
}
cache.storages[Tuple256{addr, key}] = value
}
// TxCache.storage
//-------------------------------------
// These updates do not have to be in deterministic order,
// the backend is responsible for ordering updates.
func (cache *TxCache) Sync() {
// Remove or update storage
for addrKey, value := range cache.storages {
addr, key := Tuple256Split(addrKey)
cache.backend.SetStorage(addr, key, value)
}
// Remove or update accounts
for addr, accInfo := range cache.accounts {
acc, removed := vmUnpack(accInfo)
if removed {
cache.backend.RemoveAccount(addr.Prefix(20))
} else {
cache.backend.UpdateAccount(toStateAccount(acc))
}
}
// TODO support logs, add them to the cache somehow.
}
func (cache *TxCache) AddLog(log *vm.Log) {
cache.logs = append(cache.logs, log)
}
//-----------------------------------------------------------------------------
// Convenience function to return address of new contract
func NewContractAddress(caller []byte, nonce uint64) []byte {
temp := make([]byte, 32+8)
copy(temp, caller)
PutUint64(temp[32:], nonce)
return sha3.Sha3(temp)[:20]
}
// Converts backend.Account to vm.Account struct.
func toVMAccount(acc *ac.Account) *vm.Account {
return &vm.Account{
Address: RightPadWord256(acc.Address),
Balance: acc.Balance,
Code: acc.Code, // This is crazy.
Nonce: uint64(acc.Sequence),
StorageRoot: RightPadWord256(acc.StorageRoot),
Other: acc.PubKey,
}
}
// Converts vm.Account to backend.Account struct.
func toStateAccount(acc *vm.Account) *ac.Account {
pubKey, ok := acc.Other.(ac.PubKey)
if !ok {
pubKey = ac.PubKeyNil{}
}
var storageRoot []byte
if acc.StorageRoot.IsZero() {
storageRoot = nil
} else {
storageRoot = acc.StorageRoot.Bytes()
}
return &ac.Account{
Address: acc.Address.Prefix(20),
PubKey: pubKey,
Balance: acc.Balance,
Code: acc.Code,
Sequence: uint(acc.Nonce),
StorageRoot: storageRoot,
}
}
type vmAccountInfo struct {
account *vm.Account
removed bool
}
func vmUnpack(accInfo vmAccountInfo) (*vm.Account, bool) {
return accInfo.account, accInfo.removed
}

View File

@ -1,265 +0,0 @@
package state
import (
"bytes"
"sort"
ac "github.com/tendermint/tendermint/account"
"github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/merkle"
"github.com/tendermint/tendermint/vm"
"github.com/tendermint/tendermint/vm/sha3"
)
// Converts state.Account to vm.Account struct.
func toVMAccount(acc *ac.Account) *vm.Account {
return &vm.Account{
Address: vm.BytesToWord(acc.Address),
Balance: acc.Balance,
Code: acc.Code, // This is crazy.
Nonce: uint64(acc.Sequence),
StorageRoot: vm.BytesToWord(acc.StorageRoot),
Other: acc.PubKey,
}
}
// Converts vm.Account to state.Account struct.
func toStateAccount(acc *vm.Account) *ac.Account {
pubKey, ok := acc.Other.(ac.PubKey)
if !ok {
pubKey = ac.PubKeyNil{}
}
var storageRoot []byte
if acc.StorageRoot.IsZero() {
storageRoot = nil
} else {
storageRoot = acc.StorageRoot.Bytes()
}
return &ac.Account{
Address: acc.Address.Address(),
PubKey: pubKey,
Balance: acc.Balance,
Code: acc.Code,
Sequence: uint(acc.Nonce),
StorageRoot: storageRoot,
}
}
//-----------------------------------------------------------------------------
type AccountInfo struct {
account *vm.Account
deleted bool
}
type VMAppState struct {
state *State
accounts map[string]AccountInfo
storage map[string]vm.Word
logs []*vm.Log
}
func NewVMAppState(state *State) *VMAppState {
return &VMAppState{
state: state,
accounts: make(map[string]AccountInfo),
storage: make(map[string]vm.Word),
logs: make([]*vm.Log, 0),
}
}
func unpack(accInfo AccountInfo) (*vm.Account, bool) {
return accInfo.account, accInfo.deleted
}
func (vas *VMAppState) GetAccount(addr vm.Word) (*vm.Account, error) {
account, deleted := unpack(vas.accounts[addr.String()])
if deleted {
return nil, Errorf("Account was deleted: %X", addr)
} else if account != nil {
return account, nil
} else {
acc := vas.state.GetAccount(addr.Address())
if acc == nil {
return nil, Errorf("Invalid account addr: %X", addr)
}
return toVMAccount(acc), nil
}
}
func (vas *VMAppState) UpdateAccount(account *vm.Account) error {
accountInfo, ok := vas.accounts[account.Address.String()]
if !ok {
vas.accounts[account.Address.String()] = AccountInfo{account, false}
return nil
}
account, deleted := unpack(accountInfo)
if deleted {
return Errorf("Account was deleted: %X", account.Address)
} else {
vas.accounts[account.Address.String()] = AccountInfo{account, false}
return nil
}
}
func (vas *VMAppState) DeleteAccount(account *vm.Account) error {
accountInfo, ok := vas.accounts[account.Address.String()]
if !ok {
vas.accounts[account.Address.String()] = AccountInfo{account, true}
return nil
}
account, deleted := unpack(accountInfo)
if deleted {
return Errorf("Account was already deleted: %X", account.Address)
} else {
vas.accounts[account.Address.String()] = AccountInfo{account, true}
return nil
}
}
// Creates a 20 byte address and bumps the creator's nonce.
func (vas *VMAppState) CreateAccount(creator *vm.Account) (*vm.Account, error) {
// Generate an address
nonce := creator.Nonce
creator.Nonce += 1
addr := vm.RightPadWord(NewContractAddress(creator.Address.Address(), nonce))
// Create account from address.
account, deleted := unpack(vas.accounts[addr.String()])
if deleted || account == nil {
account = &vm.Account{
Address: addr,
Balance: 0,
Code: nil,
Nonce: 0,
StorageRoot: vm.Zero,
}
vas.accounts[addr.String()] = AccountInfo{account, false}
return account, nil
} else {
panic(Fmt("Could not create account, address already exists: %X", addr))
// return nil, Errorf("Account already exists: %X", addr)
}
}
func (vas *VMAppState) GetStorage(addr vm.Word, key vm.Word) (vm.Word, error) {
account, deleted := unpack(vas.accounts[addr.String()])
if account == nil {
return vm.Zero, Errorf("Invalid account addr: %X", addr)
} else if deleted {
return vm.Zero, Errorf("Account was deleted: %X", addr)
}
value, ok := vas.storage[addr.String()+key.String()]
if ok {
return value, nil
} else {
return vm.Zero, nil
}
}
// NOTE: Set value to zero to delete from the trie.
func (vas *VMAppState) SetStorage(addr vm.Word, key vm.Word, value vm.Word) (bool, error) {
account, deleted := unpack(vas.accounts[addr.String()])
if account == nil {
return false, Errorf("Invalid account addr: %X", addr)
} else if deleted {
return false, Errorf("Account was deleted: %X", addr)
}
_, ok := vas.storage[addr.String()+key.String()]
vas.storage[addr.String()+key.String()] = value
return ok, nil
}
// CONTRACT the updates are in deterministic order.
func (vas *VMAppState) Sync() {
// Determine order for accounts
addrStrs := []string{}
for addrStr := range vas.accounts {
addrStrs = append(addrStrs, addrStr)
}
sort.Strings(addrStrs)
// Update or delete accounts.
for _, addrStr := range addrStrs {
account, deleted := unpack(vas.accounts[addrStr])
if deleted {
removed := vas.state.RemoveAccount(account.Address.Address())
if !removed {
panic(Fmt("Could not remove account to be deleted: %X", account.Address))
}
} else {
if account == nil {
panic(Fmt("Account should not be nil for addr: %X", account.Address))
}
vas.state.UpdateAccount(toStateAccount(account))
}
}
// Determine order for storage updates
// The address comes first so it'll be grouped.
storageKeyStrs := []string{}
for keyStr := range vas.storage {
storageKeyStrs = append(storageKeyStrs, keyStr)
}
sort.Strings(storageKeyStrs)
// Update storage for all account/key.
storage := merkle.NewIAVLTree(
binary.BasicCodec, // TODO change
binary.BasicCodec, // TODO change
1024, // TODO change.
vas.state.DB,
)
var currentAccount *vm.Account
var deleted bool
for _, storageKey := range storageKeyStrs {
value := vas.storage[storageKey]
addrKeyBytes := []byte(storageKey)
addr := addrKeyBytes[:32]
key := addrKeyBytes[32:]
if currentAccount == nil || !bytes.Equal(currentAccount.Address[:], addr) {
currentAccount, deleted = unpack(vas.accounts[string(addr)])
if deleted {
continue
}
var storageRoot []byte
if currentAccount.StorageRoot.IsZero() {
storageRoot = nil
} else {
storageRoot = currentAccount.StorageRoot.Bytes()
}
storage.Load(storageRoot)
}
if value.IsZero() {
_, removed := storage.Remove(key)
if !removed {
panic(Fmt("Storage could not be removed for addr: %X @ %X", addr, key))
}
} else {
storage.Set(key, value)
}
}
// TODO support logs, add them to the state somehow.
}
func (vas *VMAppState) AddLog(log *vm.Log) {
vas.logs = append(vas.logs, log)
}
//-----------------------------------------------------------------------------
// Convenience function to return address of new contract
func NewContractAddress(caller []byte, nonce uint64) []byte {
temp := make([]byte, 32+8)
copy(temp, caller)
vm.PutUint64(temp[32:], nonce)
return sha3.Sha3(temp)[:20]
}

View File

@ -55,16 +55,24 @@ func (b *Block) ValidateBasic(lastBlockHeight uint, lastBlockHash []byte,
return nil
}
// Computes and returns the block hash.
// If the block is incomplete (e.g. missing Header.StateHash)
// then the hash is nil, to prevent the usage of that hash.
func (b *Block) Hash() []byte {
if b.Header == nil || b.Validation == nil || b.Data == nil {
return nil
}
hashes := [][]byte{
b.Header.Hash(),
b.Validation.Hash(),
b.Data.Hash(),
hashHeader := b.Header.Hash()
hashValidation := b.Validation.Hash()
hashData := b.Data.Hash()
// If hashHeader is nil, required fields are missing.
if len(hashHeader) == 0 {
return nil
}
// Merkle hash from sub-hashes.
// Merkle hash from subhashes.
hashes := [][]byte{hashHeader, hashValidation, hashData}
return merkle.HashFromHashes(hashes)
}
@ -125,7 +133,12 @@ type Header struct {
StateHash []byte
}
// NOTE: hash is nil if required fields are missing.
func (h *Header) Hash() []byte {
if len(h.StateHash) == 0 {
return nil
}
buf := new(bytes.Buffer)
hasher, n, err := sha256.New(), new(int64), new(error)
binary.WriteBinary(h, buf, n, err)

View File

@ -254,3 +254,10 @@ func (tx *DupeoutTx) WriteSignBytes(w io.Writer, n *int64, err *error) {
func (tx *DupeoutTx) String() string {
return Fmt("DupeoutTx{%X,%v,%v}", tx.Address, tx.VoteA, tx.VoteB)
}
//-----------------------------------------------------------------------------
func TxId(tx Tx) []byte {
signBytes := account.SignBytes(tx)
return binary.BinaryRipemd160(signBytes)
}

View File

@ -1,35 +0,0 @@
package vm
import (
"encoding/binary"
)
func Uint64ToWord(i uint64) Word {
word := Word{}
PutUint64(word[:], i)
return word
}
func BytesToWord(bz []byte) Word {
word := Word{}
copy(word[:], bz)
return word
}
func LeftPadWord(bz []byte) (word Word) {
copy(word[32-len(bz):], bz)
return
}
func RightPadWord(bz []byte) (word Word) {
copy(word[:], bz)
return
}
func GetUint64(word Word) uint64 {
return binary.LittleEndian.Uint64(word[:])
}
func PutUint64(dest []byte, i uint64) {
binary.LittleEndian.PutUint64(dest, i)
}

View File

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

View File

@ -3,19 +3,18 @@ package vm
import (
"code.google.com/p/go.crypto/ripemd160"
"crypto/sha256"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm/secp256k1"
"github.com/tendermint/tendermint/vm/sha3"
. "github.com/tendermint/tendermint/common"
)
var nativeContracts = make(map[Word]NativeContract)
var nativeContracts = make(map[Word256]NativeContract)
func init() {
nativeContracts[Uint64ToWord(1)] = ecrecoverFunc
nativeContracts[Uint64ToWord(2)] = sha256Func
nativeContracts[Uint64ToWord(3)] = ripemd160Func
nativeContracts[Uint64ToWord(4)] = identityFunc
nativeContracts[Uint64ToWord256(1)] = ecrecoverFunc
nativeContracts[Uint64ToWord256(2)] = sha256Func
nativeContracts[Uint64ToWord256(3)] = ripemd160Func
nativeContracts[Uint64ToWord256(4)] = identityFunc
}
//-----------------------------------------------------------------------------

View File

@ -2,11 +2,12 @@ package vm
import (
"fmt"
. "github.com/tendermint/tendermint/common"
)
// Not goroutine safe
type Stack struct {
data []Word
data []Word256
ptr int
gas *uint64
@ -15,7 +16,7 @@ type Stack struct {
func NewStack(capacity int, gas *uint64, err *error) *Stack {
return &Stack{
data: make([]Word, capacity),
data: make([]Word256, capacity),
ptr: 0,
gas: gas,
err: err,
@ -36,7 +37,7 @@ func (st *Stack) setErr(err error) {
}
}
func (st *Stack) Push(d Word) {
func (st *Stack) Push(d Word256) {
st.useGas(GasStackOp)
if st.ptr == cap(st.data) {
st.setErr(ErrDataStackOverflow)
@ -50,18 +51,18 @@ func (st *Stack) PushBytes(bz []byte) {
if len(bz) != 32 {
panic("Invalid bytes size: expected 32")
}
st.Push(BytesToWord(bz))
st.Push(RightPadWord256(bz))
}
func (st *Stack) Push64(i uint64) {
st.Push(Uint64ToWord(i))
st.Push(Uint64ToWord256(i))
}
func (st *Stack) Pop() Word {
func (st *Stack) Pop() Word256 {
st.useGas(GasStackOp)
if st.ptr == 0 {
st.setErr(ErrDataStackUnderflow)
return Zero
return Zero256
}
st.ptr--
return st.data[st.ptr]
@ -72,7 +73,7 @@ func (st *Stack) PopBytes() []byte {
}
func (st *Stack) Pop64() uint64 {
return GetUint64(st.Pop())
return GetUint64(st.Pop().Bytes())
}
func (st *Stack) Len() int {
@ -100,7 +101,7 @@ func (st *Stack) Dup(n int) {
}
// Not an opcode, costs no gas.
func (st *Stack) Peek() Word {
func (st *Stack) Peek() Word256 {
return st.data[st.ptr-1]
}

View File

@ -1,8 +1,6 @@
package main
package vm
import (
"fmt"
. "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/vm"
"github.com/tendermint/tendermint/vm/sha3"
@ -10,41 +8,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,75 +49,46 @@ 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) {
fas.logs = append(fas.logs, log)
}
func main() {
appState := &FakeAppState{
accounts: make(map[string]*Account),
storage: make(map[string]Word),
logs: nil,
}
params := Params{
BlockHeight: 0,
BlockHash: Zero,
BlockTime: 0,
GasLimit: 0,
}
ourVm := NewVM(appState, params, Zero)
// Create accounts
account1 := &Account{
Address: Uint64ToWord(100),
}
account2 := &Account{
Address: Uint64ToWord(101),
}
var gas uint64 = 1000
output, err := ourVm.Call(account1, account2, []byte{0x5B, 0x60, 0x00, 0x56}, []byte{}, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
}
// 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])
}

99
vm/test/vm_test.go Normal file
View File

@ -0,0 +1,99 @@
package vm
import (
"crypto/rand"
"encoding/hex"
"fmt"
"strings"
"testing"
"time"
. "github.com/tendermint/tendermint/common"
. "github.com/tendermint/tendermint/vm"
)
func newAppState() *FakeAppState {
return &FakeAppState{
accounts: make(map[string]*Account),
storage: make(map[string]Word256),
logs: nil,
}
}
func newParams() Params {
return Params{
BlockHeight: 0,
BlockHash: Zero256,
BlockTime: 0,
GasLimit: 0,
}
}
func makeBytes(n int) []byte {
b := make([]byte, n)
rand.Read(b)
return b
}
func TestVM(t *testing.T) {
ourVm := NewVM(newAppState(), newParams(), Zero256)
// Create accounts
account1 := &Account{
Address: Uint64ToWord256(100),
}
account2 := &Account{
Address: Uint64ToWord256(101),
}
var gas uint64 = 1000
N := []byte{0xff, 0xff}
// Loop N times
code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)}
for i := 0; i < len(N); i++ {
code = append(code, N[i])
}
code = append(code, []byte{0x60, 0x20, 0x51, 0x12, 0x15, 0x60, byte(0x1b + len(N)), 0x57, 0x60, 0x01, 0x60, 0x20, 0x51, 0x01, 0x60, 0x20, 0x52, 0x60, 0x05, 0x56, 0x5B}...)
start := time.Now()
output, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
fmt.Println("Call took:", time.Since(start))
}
func TestSubcurrency(t *testing.T) {
st := newAppState()
// Create accounts
account1 := &Account{
Address: RightPadWord256(makeBytes(20)),
}
account2 := &Account{
Address: RightPadWord256(makeBytes(20)),
}
st.accounts[account1.Address.String()] = account1
st.accounts[account2.Address.String()] = account2
ourVm := NewVM(st, newParams(), Zero256)
var gas uint64 = 1000
code_parts := []string{"620f42403355",
"7c0100000000000000000000000000000000000000000000000000000000",
"600035046315cf268481141561004657",
"6004356040526040515460605260206060f35b63693200ce81141561008757",
"60043560805260243560a052335460c0523360e05260a05160c05112151561008657",
"60a05160c0510360e0515560a0516080515401608051555b5b505b6000f3"}
code, _ := hex.DecodeString(strings.Join(code_parts, ""))
fmt.Printf("Code: %x\n", code)
data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005")
output, err := ourVm.Call(account1, account2, code, data, 0, &gas)
fmt.Printf("Output: %v Error: %v\n", output, err)
}
/*
// infinite loop
code := []byte{0x5B, 0x60, 0x00, 0x56}
// mstore
code := []byte{0x60, 0x00, 0x60, 0x20}
// mstore, mload
code := []byte{0x60, 0x01, 0x60, 0x20, 0x52, 0x60, 0x20, 0x51}
*/

View File

@ -1,44 +1,25 @@
package vm
import ()
import (
. "github.com/tendermint/tendermint/common"
)
const (
defaultDataStackCapacity = 10
)
var (
Zero = Word{0}
One = Word{1}
)
type Word [32]byte
func (w Word) String() string { return string(w[:]) }
func (w Word) Copy() Word { return w }
func (w Word) Bytes() []byte { return w[:] } // copied.
func (w Word) Address() []byte { return w[:20] }
func (w Word) IsZero() bool {
accum := byte(0)
for _, byt := range w {
accum |= byt
}
return accum == 0
}
//-----------------------------------------------------------------------------
type Account struct {
Address Word
Address Word256
Balance uint64
Code []byte
Nonce uint64
StorageRoot Word
StorageRoot Word256
Other interface{} // For holding all other data.
}
type Log struct {
Address Word
Topics []Word
Address Word256
Topics []Word256
Data []byte
Height uint64
}
@ -46,14 +27,14 @@ type Log struct {
type AppState interface {
// Accounts
GetAccount(addr Word) (*Account, error)
UpdateAccount(*Account) error
DeleteAccount(*Account) error
CreateAccount(*Account) (*Account, error)
GetAccount(addr Word256) *Account
UpdateAccount(*Account)
RemoveAccount(*Account)
CreateAccount(*Account) *Account
// Storage
GetStorage(Word, Word) (Word, error)
SetStorage(Word, Word, Word) (bool, error) // Setting to Zero is deleting.
GetStorage(Word256, Word256) Word256
SetStorage(Word256, Word256, Word256) // Setting to Zero is deleting.
// Logs
AddLog(*Log)
@ -61,7 +42,7 @@ type AppState interface {
type Params struct {
BlockHeight uint64
BlockHash Word
BlockHash Word256
BlockTime int64
GasLimit uint64
}

367
vm/vm.go
View File

@ -3,13 +3,14 @@ package vm
import (
"errors"
"fmt"
"math"
"math/big"
. "github.com/tendermint/tendermint/common"
"github.com/tendermint/tendermint/vm/sha3"
)
var (
ErrUnknownAddress = errors.New("Unknown address")
ErrInsufficientBalance = errors.New("Insufficient balance")
ErrInvalidJumpDest = errors.New("Invalid jump dest")
ErrInsufficientGas = errors.New("Insuffient gas")
@ -23,21 +24,30 @@ var (
ErrInvalidContract = errors.New("Invalid contract")
)
type Debug bool
const (
dataStackCapacity = 1024
callStackCapacity = 100 // TODO ensure usage.
memoryCapacity = 1024 * 1024 // 1 MB
dataStackCapacity = 1024
callStackCapacity = 100 // TODO ensure usage.
memoryCapacity = 1024 * 1024 // 1 MB
dbg Debug = true
)
func (d Debug) Printf(s string, a ...interface{}) {
if d {
fmt.Printf(s, a...)
}
}
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,
@ -73,7 +83,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga
// Just like Call() but does not transfer 'value' or modify the callDepth.
func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) {
fmt.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input)
dbg.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input)
var (
pc uint64 = 0
@ -89,7 +99,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
}
var op = codeGetOp(code, pc)
fmt.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len())
dbg.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len())
switch op {
@ -97,164 +107,197 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, nil
case ADD: // 0x01
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x + y)
fmt.Printf(" %v + %v = %v\n", x, y, x+y)
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(x + y)
x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
sum := new(big.Int).Add(xb, yb)
stack.Push(RightPadWord256(flip(sum.Bytes())))
dbg.Printf(" %v + %v = %v\n", xb, yb, sum)
case MUL: // 0x02
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x * y)
fmt.Printf(" %v * %v = %v\n", x, y, x*y)
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(x * y)
x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
prod := new(big.Int).Mul(xb, yb)
stack.Push(RightPadWord256(flip(prod.Bytes())))
dbg.Printf(" %v * %v = %v\n", xb, yb, prod)
case SUB: // 0x03
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x - y)
fmt.Printf(" %v - %v = %v\n", x, y, x-y)
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(x - y)
x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
diff := new(big.Int).Sub(xb, yb)
stack.Push(RightPadWord256(flip(diff.Bytes())))
dbg.Printf(" %v - %v = %v\n", xb, yb, diff)
case DIV: // 0x04
x, y := stack.Pop64(), stack.Pop64()
if y == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(x / y)
x, y := stack.Pop(), stack.Pop()
if y.IsZero() { // TODO
stack.Push(Zero256)
dbg.Printf(" %x / %x = %v (TODO)\n", x, y, 0)
} else {
stack.Push64(x / y)
fmt.Printf(" %v / %v = %v\n", x, y, x/y)
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
div := new(big.Int).Div(xb, yb)
stack.Push(RightPadWord256(flip(div.Bytes())))
dbg.Printf(" %v / %v = %v\n", xb, yb, div)
}
case SDIV: // 0x05
// TODO ... big?
x, y := int64(stack.Pop64()), int64(stack.Pop64())
if y == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
stack.Push(Zero256)
dbg.Printf(" %v / %v = %v (TODO)\n", x, y, 0)
} else {
stack.Push64(uint64(x / y))
fmt.Printf(" %v / %v = %v\n", x, y, x/y)
dbg.Printf(" %v / %v = %v\n", x, y, x/y)
}
case MOD: // 0x06
x, y := stack.Pop64(), stack.Pop64()
if y == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
//x, y := stack.Pop64(), stack.Pop64()
x, y := stack.Pop(), stack.Pop()
if y.IsZero() { // TODO
stack.Push(Zero256)
dbg.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
} else {
stack.Push64(x % y)
fmt.Printf(" %v %% %v = %v\n", x, y, x%y)
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
mod := new(big.Int).Mod(xb, yb)
stack.Push(RightPadWord256(flip(mod.Bytes())))
dbg.Printf(" %v %% %v = %v\n", xb, yb, mod)
}
case SMOD: // 0x07
// TODO ... big?
x, y := int64(stack.Pop64()), int64(stack.Pop64())
if y == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
stack.Push(Zero256)
dbg.Printf(" %v %% %v = %v (TODO)\n", x, y, 0)
} else {
stack.Push64(uint64(x % y))
fmt.Printf(" %v %% %v = %v\n", x, y, x%y)
dbg.Printf(" %v %% %v = %v\n", x, y, x%y)
}
case ADDMOD: // 0x08
// TODO ... big?
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
if z == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
stack.Push(Zero256)
dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
} else {
stack.Push64(x % y)
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x+y)%z)
stack.Push64((x + y) % z)
dbg.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x+y)%z)
}
case MULMOD: // 0x09
// TODO ... big?
x, y, z := stack.Pop64(), stack.Pop64(), stack.Pop64()
if z == 0 { // TODO
stack.Push(Zero)
fmt.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
stack.Push(Zero256)
dbg.Printf(" (%v + %v) %% %v = %v (TODO)\n", x, y, z, 0)
} else {
stack.Push64(x % y)
fmt.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x*y)%z)
stack.Push64((x * y) % z)
dbg.Printf(" (%v + %v) %% %v = %v\n", x, y, z, (x*y)%z)
}
case EXP: // 0x0A
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(ExpUint64(x, y))
fmt.Printf(" %v ** %v = %v\n", x, y, uint64(math.Pow(float64(x), float64(y))))
//x, y := stack.Pop64(), stack.Pop64()
//stack.Push64(ExpUint64(x, y))
x, y := stack.Pop(), stack.Pop()
xb := new(big.Int).SetBytes(flip(x[:]))
yb := new(big.Int).SetBytes(flip(y[:]))
pow := new(big.Int).Exp(xb, yb, big.NewInt(0))
stack.Push(RightPadWord256(flip(pow.Bytes())))
dbg.Printf(" %v ** %v = %v\n", xb, yb, pow)
case SIGNEXTEND: // 0x0B
x, y := stack.Pop64(), stack.Pop64()
res := (y << uint(x)) >> x
stack.Push64(res)
fmt.Printf(" (%v << %v) >> %v = %v\n", y, x, x, res)
dbg.Printf(" (%v << %v) >> %v = %v\n", y, x, x, res)
case LT: // 0x10
x, y := stack.Pop64(), stack.Pop64()
if x < y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v < %v = %v\n", x, y, x < y)
dbg.Printf(" %v < %v = %v\n", x, y, x < y)
case GT: // 0x11
x, y := stack.Pop64(), stack.Pop64()
if x > y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v > %v = %v\n", x, y, x > y)
dbg.Printf(" %v > %v = %v\n", x, y, x > y)
case SLT: // 0x12
x, y := int64(stack.Pop64()), int64(stack.Pop64())
if x < y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v < %v = %v\n", x, y, x < y)
dbg.Printf(" %v < %v = %v\n", x, y, x < y)
case SGT: // 0x13
x, y := int64(stack.Pop64()), int64(stack.Pop64())
if x > y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v > %v = %v\n", x, y, x > y)
dbg.Printf(" %v > %v = %v\n", x, y, x > y)
case EQ: // 0x14
x, y := stack.Pop64(), stack.Pop64()
if x > y {
if x == y {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v == %v = %v\n", x, y, x == y)
dbg.Printf(" %v == %v = %v\n", x, y, x == y)
case ISZERO: // 0x15
x := stack.Pop64()
if x == 0 {
stack.Push64(1)
} else {
stack.Push(Zero)
stack.Push(Zero256)
}
fmt.Printf(" %v == 0 = %v\n", x, x == 0)
dbg.Printf(" %v == 0 = %v\n", x, x == 0)
case AND: // 0x16
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x & y)
fmt.Printf(" %v & %v = %v\n", x, y, x&y)
dbg.Printf(" %v & %v = %v\n", x, y, x&y)
case OR: // 0x17
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x | y)
fmt.Printf(" %v | %v = %v\n", x, y, x|y)
dbg.Printf(" %v | %v = %v\n", x, y, x|y)
case XOR: // 0x18
x, y := stack.Pop64(), stack.Pop64()
stack.Push64(x ^ y)
fmt.Printf(" %v ^ %v = %v\n", x, y, x^y)
dbg.Printf(" %v ^ %v = %v\n", x, y, x^y)
case NOT: // 0x19
x := stack.Pop64()
stack.Push64(^x)
fmt.Printf(" !%v = %v\n", x, ^x)
dbg.Printf(" !%v = %v\n", x, ^x)
case BYTE: // 0x1A
idx, val := stack.Pop64(), stack.Pop()
@ -263,7 +306,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
res = val[idx]
}
stack.Push64(uint64(res))
fmt.Printf(" => 0x%X\n", res)
dbg.Printf(" => 0x%X\n", res)
case SHA3: // 0x20
if ok = useGas(gas, GasSha3); !ok {
@ -276,36 +319,36 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
}
data = sha3.Sha3(data)
stack.PushBytes(data)
fmt.Printf(" => (%v) %X\n", size, data)
dbg.Printf(" => (%v) %X\n", size, data)
case ADDRESS: // 0x30
stack.Push(callee.Address)
fmt.Printf(" => %X\n", callee.Address)
dbg.Printf(" => %X\n", callee.Address)
case BALANCE: // 0x31
addr := stack.Pop()
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)
dbg.Printf(" => %v (%X)\n", balance, addr)
case ORIGIN: // 0x32
stack.Push(vm.origin)
fmt.Printf(" => %X\n", vm.origin)
dbg.Printf(" => %X\n", vm.origin)
case CALLER: // 0x33
stack.Push(caller.Address)
fmt.Printf(" => %X\n", caller.Address)
dbg.Printf(" => %X\n", caller.Address)
case CALLVALUE: // 0x34
stack.Push64(value)
fmt.Printf(" => %v\n", value)
dbg.Printf(" => %v\n", value)
case CALLDATALOAD: // 0x35
offset := stack.Pop64()
@ -313,12 +356,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok {
return nil, firstErr(err, ErrInputOutOfBounds)
}
stack.Push(RightPadWord(data))
fmt.Printf(" => 0x%X\n", data)
stack.Push(RightPadWord256(data))
dbg.Printf(" => 0x%X\n", data)
case CALLDATASIZE: // 0x36
stack.Push64(uint64(len(input)))
fmt.Printf(" => %d\n", len(input))
dbg.Printf(" => %d\n", len(input))
case CALLDATACOPY: // 0x37
memOff := stack.Pop64()
@ -333,18 +376,17 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
copy(dest, data)
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data)
dbg.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data)
case CODESIZE: // 0x38
l := uint64(len(code))
stack.Push64(l)
fmt.Printf(" => %d\n", l)
dbg.Printf(" => %d\n", l)
case CODECOPY: // 0x39
memOff := stack.Pop64()
codeOff := stack.Pop64()
length := stack.Pop64()
fmt.Println("CODECOPY: codeOff, length, codelength", codeOff, length, len(code))
data, ok := subslice(code, codeOff, length, false)
if !ok {
return nil, firstErr(err, ErrCodeOutOfBounds)
@ -354,36 +396,36 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
copy(dest, data)
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
case GASPRICE_DEPRECATED: // 0x3A
stack.Push(Zero)
fmt.Printf(" => %X (GASPRICE IS DEPRECATED)\n")
stack.Push(Zero256)
dbg.Printf(" => %X (GASPRICE IS DEPRECATED)\n")
case EXTCODESIZE: // 0x3B
addr := stack.Pop()
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)
dbg.Printf(" => %d\n", l)
case EXTCODECOPY: // 0x3C
addr := stack.Pop()
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()
@ -396,33 +438,33 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
copy(dest, data)
fmt.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data)
case BLOCKHASH: // 0x40
stack.Push(Zero)
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
stack.Push(Zero256)
dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
case COINBASE: // 0x41
stack.Push(Zero)
fmt.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
stack.Push(Zero256)
dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes())
case TIMESTAMP: // 0x42
time := vm.params.BlockTime
stack.Push64(uint64(time))
fmt.Printf(" => 0x%X\n", time)
dbg.Printf(" => 0x%X\n", time)
case BLOCKHEIGHT: // 0x43
number := uint64(vm.params.BlockHeight)
stack.Push64(number)
fmt.Printf(" => 0x%X\n", number)
dbg.Printf(" => 0x%X\n", number)
case GASLIMIT: // 0x45
stack.Push64(vm.params.GasLimit)
fmt.Printf(" => %v\n", vm.params.GasLimit)
dbg.Printf(" => %v\n", vm.params.GasLimit)
case POP: // 0x50
stack.Pop()
fmt.Printf(" => %v\n", vm.params.GasLimit)
dbg.Printf(" => %v\n", vm.params.GasLimit)
case MLOAD: // 0x51
offset := stack.Pop64()
@ -430,17 +472,17 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
stack.Push(RightPadWord(data))
fmt.Printf(" => 0x%X\n", data)
stack.Push(RightPadWord256(data))
dbg.Printf(" => 0x%X\n", data)
case MSTORE: // 0x52
offset, data := stack.Pop64(), stack.Pop()
dest, ok := subslice(memory, offset, 32, true)
dest, ok := subslice(memory, offset, 32, false)
if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
copy(dest, data[:])
fmt.Printf(" => 0x%X\n", data)
copy(dest, flip(data[:]))
dbg.Printf(" => 0x%X\n", data)
case MSTORE8: // 0x53
offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF)
@ -448,26 +490,19 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
memory[offset] = val
fmt.Printf(" => [%v] 0x%X\n", offset, val)
dbg.Printf(" => [%v] 0x%X\n", offset, val)
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)
dbg.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)
}
fmt.Printf(" {0x%X : 0x%X}\n", loc, data)
vm.appState.SetStorage(callee.Address, loc, data)
useGas(gas, GasStorageUpdate)
dbg.Printf(" {0x%X : 0x%X}\n", loc, data)
case JUMP: // 0x56
err = jump(code, stack.Pop64(), &pc)
@ -479,7 +514,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
err = jump(code, pos, &pc)
continue
}
fmt.Printf(" ~> false\n")
dbg.Printf(" ~> false\n")
case PC: // 0x58
stack.Push64(pc)
@ -489,10 +524,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
case GAS: // 0x5A
stack.Push64(*gas)
fmt.Printf(" => %X\n", *gas)
dbg.Printf(" => %X\n", *gas)
case JUMPDEST: // 0x5B
fmt.Printf("\n")
dbg.Printf("\n")
// Do nothing
case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32:
@ -501,24 +536,24 @@ 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)
dbg.Printf(" => 0x%X\n", res)
case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16:
n := int(op - DUP1 + 1)
stack.Dup(n)
fmt.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes())
dbg.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes())
case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16:
n := int(op - SWAP1 + 2)
stack.Swap(n)
fmt.Printf(" => [%d]\n", n)
dbg.Printf(" => [%d]\n", n)
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()
@ -534,7 +569,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
vm.params.BlockHeight,
}
vm.appState.AddLog(log)
fmt.Printf(" => %v\n", log)
dbg.Printf(" => %v\n", log)
case CREATE: // 0xF0
contractValue := stack.Pop64()
@ -551,19 +586,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
@ -571,7 +601,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
addr, value := stack.Pop(), stack.Pop64()
inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs
retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs
fmt.Printf(" => %X\n", addr)
dbg.Printf(" => %X\n", addr)
// Get the arguments from the memory
args, ok := subslice(memory, inOffset, inSize, false)
@ -598,22 +628,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)
@ -624,7 +654,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
// Handle remaining gas.
*gas += gasLimit
fmt.Printf("resume %X (%v)\n", callee.Address, gas)
dbg.Printf("resume %X (%v)\n", callee.Address, gas)
case RETURN: // 0xF3
offset, size := stack.Pop64(), stack.Pop64()
@ -632,7 +662,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga
if !ok {
return nil, firstErr(err, ErrMemoryOutOfBounds)
}
fmt.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret)
dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret)
return ret, nil
case SUICIDE: // 0xFF
@ -640,20 +670,20 @@ 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 , 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)
fmt.Printf(" => (%X) %v\n", addr[:4], balance)
vm.appState.RemoveAccount(callee)
dbg.Printf(" => (%X) %v\n", addr[:4], balance)
fallthrough
default:
fmt.Printf("(pc) %-3v Invalid opcode %X\n", pc, op)
dbg.Printf("(pc) %-3v Invalid opcode %X\n", pc, op)
panic(fmt.Errorf("Invalid opcode %X", op))
}
@ -688,10 +718,10 @@ func codeGetOp(code []byte, n uint64) OpCode {
func jump(code []byte, to uint64, pc *uint64) (err error) {
dest := codeGetOp(code, to)
if dest != JUMPDEST {
fmt.Printf(" ~> %v invalid jump dest %v\n", to, dest)
dbg.Printf(" ~> %v invalid jump dest %v\n", to, dest)
return ErrInvalidJumpDest
}
fmt.Printf(" ~> %v\n", to)
dbg.Printf(" ~> %v\n", to)
*pc = to
return nil
}
@ -724,10 +754,25 @@ func transfer(from, to *Account, amount uint64) error {
}
func flip(in []byte) []byte {
l2 := len(in) / 2
flipped := make([]byte, len(in))
for i := 0; i < len(flipped)/2; i++ {
// copy the middle bit (if its even it will get overwritten)
if len(in) != 0 {
flipped[l2] = in[l2]
}
for i := 0; i < l2; i++ {
flipped[i] = in[len(in)-1-i]
flipped[len(in)-1-i] = in[i]
}
return flipped
}
func flipWord(in Word256) Word256 {
word := Word256{}
// copy the middle bit (if its even it will get overwritten)
for i := 0; i < 16; i++ {
word[i] = in[len(in)-1-i]
word[len(in)-1-i] = in[i]
}
return word
}