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