From 4956836f2daa844814eadffa6e4aa2c80ca2a7c7 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 2 Apr 2015 03:06:46 -0700 Subject: [PATCH 1/8] rpc: cleanup, use client for tests, rpc-gen fixes --- rpc/client.go | 74 +++------------ rpc/client_methods.go | 84 +++++++++-------- rpc/core/accounts.go | 10 +- rpc/handlers.go | 20 ++++ rpc/test/http_rpc_test.go | 54 ++--------- rpc/test/test.go | 190 +++++++++++++------------------------- 6 files changed, 155 insertions(+), 277 deletions(-) diff --git a/rpc/client.go b/rpc/client.go index 19b32a9b7..242dffa11 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "net/http" "net/url" - "reflect" + //"reflect" // Uncomment to use go:generate // _ "github.com/ebuchman/go-rpc-gen" ) @@ -38,67 +38,21 @@ func NewClient(addr, typ string) Client { return nil } -func argsToJson(args ...interface{}) ([][]string, error) { +func argsToJson(args ...interface{}) ([]string, error) { l := len(args) - jsons := make([][]string, l) + jsons := make([]string, l) n, err := new(int64), new(error) for i, a := range args { - //if its a slice, we serliaze separately and pack into a slice of strings - // otherwise its a slice of length 1 - if v := reflect.ValueOf(a); v.Kind() == reflect.Slice { - ty := v.Type() - rt := ty.Elem() - if rt.Kind() == reflect.Uint8 { - buf := new(bytes.Buffer) - binary.WriteJSON(a, buf, n, err) - if *err != nil { - return nil, *err - } - jsons[i] = []string{string(buf.Bytes())} - } else { - slice := make([]string, v.Len()) - for j := 0; j < v.Len(); j++ { - buf := new(bytes.Buffer) - binary.WriteJSON(v.Index(j).Interface(), buf, n, err) - if *err != nil { - return nil, *err - } - slice[j] = string(buf.Bytes()) - } - jsons[i] = slice - } - } else { - buf := new(bytes.Buffer) - binary.WriteJSON(a, buf, n, err) - if *err != nil { - return nil, *err - } - jsons[i] = []string{string(buf.Bytes())} + buf := new(bytes.Buffer) + binary.WriteJSON(a, buf, n, err) + if *err != nil { + return nil, *err } + jsons[i] = string(buf.Bytes()) } return jsons, nil } -func (c *ClientHTTP) RequestResponse(method string, values url.Values) (*Response, error) { - resp, err := http.PostForm(c.addr+method, values) - if err != nil { - return nil, err - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - response := new(Response) - fmt.Println(string(body)) - binary.ReadJSON(response, body, &err) - if err != nil { - return nil, err - } - fmt.Println(response.Data) - return response, nil -} - func (c *ClientJSON) RequestResponse(s RPCRequest) (b []byte, err error) { b = binary.JSONBytes(s) buf := bytes.NewBuffer(b) @@ -153,10 +107,10 @@ func argsToURLValues(argNames []string, args ...interface{}) (url.Values, error) } for i, name := range argNames { s := slice[i] - values.Set(name, s[0]) - for i := 1; i < len(s); i++ { - values.Add(name, s[i]) - } + values.Set(name, s) // s[0] + /*for j := 1; j < len(s); j++ { + values.Add(name, s[j]) + }*/ } return values, nil } @@ -175,7 +129,7 @@ fmt /*rpc-gen:template:*ClientJSON func (c *ClientJSON) {{name}}({{args.def}}) ({{response}}) { request := RPCRequest{ JSONRPC: "2.0", - Method: {{lowername}}, + Method: reverseFuncMap["{{name}}"], Params: []interface{}{ {{args.ident}} }, Id: 0, } @@ -204,7 +158,7 @@ fmt if err != nil{ return nil, err } - resp, err := http.PostForm(c.addr+{{lowername}}, values) + resp, err := http.PostForm(c.addr+reverseFuncMap["{{name}}"], values) if err != nil { return nil, err } diff --git a/rpc/client_methods.go b/rpc/client_methods.go index 1bf5badcc..872e723e8 100644 --- a/rpc/client_methods.go +++ b/rpc/client_methods.go @@ -1,3 +1,5 @@ +// File generated by github.com/ebuchman/rpc-gen + package rpc import ( @@ -11,14 +13,14 @@ import ( ) type Client interface { - BlockchainInfo(minHeight uint) (*core.ResponseBlockchainInfo, error) + BlockchainInfo(minHeight uint, maxHeight uint) (*core.ResponseBlockchainInfo, error) BroadcastTx(tx types.Tx) (*core.ResponseBroadcastTx, error) - Call(address []byte) (*core.ResponseCall, error) + Call(address []byte, data []byte) (*core.ResponseCall, error) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) GenPrivAccount() (*core.ResponseGenPrivAccount, error) GetAccount(address []byte) (*core.ResponseGetAccount, error) GetBlock(height uint) (*core.ResponseGetBlock, error) - GetStorage(address []byte) (*core.ResponseGetStorage, error) + GetStorage(address []byte, storage []byte) (*core.ResponseGetStorage, error) ListAccounts() (*core.ResponseListAccounts, error) ListValidators() (*core.ResponseListValidators, error) NetInfo() (*core.ResponseNetInfo, error) @@ -26,12 +28,12 @@ type Client interface { Status() (*core.ResponseStatus, error) } -func (c *ClientHTTP) BlockchainInfo(minHeight uint) (*core.ResponseBlockchainInfo, error) { - values, err := argsToURLValues([]string{"minHeight"}, minHeight) +func (c *ClientHTTP) BlockchainInfo(minHeight uint, maxHeight uint) (*core.ResponseBlockchainInfo, error) { + values, err := argsToURLValues([]string{"minHeight", "maxHeight"}, minHeight, maxHeight) if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"blockchain_info", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["BlockchainInfo"], values) if err != nil { return nil, err } @@ -61,7 +63,7 @@ func (c *ClientHTTP) BroadcastTx(tx types.Tx) (*core.ResponseBroadcastTx, error) if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"broadcast_tx", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["BroadcastTx"], values) if err != nil { return nil, err } @@ -86,12 +88,12 @@ func (c *ClientHTTP) BroadcastTx(tx types.Tx) (*core.ResponseBroadcastTx, error) return response.Result, nil } -func (c *ClientHTTP) Call(address []byte) (*core.ResponseCall, error) { - values, err := argsToURLValues([]string{"address"}, address) +func (c *ClientHTTP) Call(address []byte, data []byte) (*core.ResponseCall, error) { + values, err := argsToURLValues([]string{"address", "data"}, address, data) if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"call", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["Call"], values) if err != nil { return nil, err } @@ -121,7 +123,7 @@ func (c *ClientHTTP) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"dump_storage", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["DumpStorage"], values) if err != nil { return nil, err } @@ -151,7 +153,7 @@ func (c *ClientHTTP) GenPrivAccount() (*core.ResponseGenPrivAccount, error) { if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"gen_priv_account", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["GenPrivAccount"], values) if err != nil { return nil, err } @@ -181,7 +183,7 @@ func (c *ClientHTTP) GetAccount(address []byte) (*core.ResponseGetAccount, error if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"get_account", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["GetAccount"], values) if err != nil { return nil, err } @@ -211,7 +213,7 @@ func (c *ClientHTTP) GetBlock(height uint) (*core.ResponseGetBlock, error) { if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"get_block", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["GetBlock"], values) if err != nil { return nil, err } @@ -236,12 +238,12 @@ func (c *ClientHTTP) GetBlock(height uint) (*core.ResponseGetBlock, error) { return response.Result, nil } -func (c *ClientHTTP) GetStorage(address []byte) (*core.ResponseGetStorage, error) { - values, err := argsToURLValues([]string{"address"}, address) +func (c *ClientHTTP) GetStorage(address []byte, storage []byte) (*core.ResponseGetStorage, error) { + values, err := argsToURLValues([]string{"address", "storage"}, address, storage) if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"get_storage", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["GetStorage"], values) if err != nil { return nil, err } @@ -271,7 +273,7 @@ func (c *ClientHTTP) ListAccounts() (*core.ResponseListAccounts, error) { if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"list_accounts", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["ListAccounts"], values) if err != nil { return nil, err } @@ -301,7 +303,7 @@ func (c *ClientHTTP) ListValidators() (*core.ResponseListValidators, error) { if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"list_validators", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["ListValidators"], values) if err != nil { return nil, err } @@ -331,7 +333,7 @@ func (c *ClientHTTP) NetInfo() (*core.ResponseNetInfo, error) { if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"net_info", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["NetInfo"], values) if err != nil { return nil, err } @@ -361,7 +363,7 @@ func (c *ClientHTTP) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (* if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"sign_tx", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["SignTx"], values) if err != nil { return nil, err } @@ -391,7 +393,7 @@ func (c *ClientHTTP) Status() (*core.ResponseStatus, error) { if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+"status", values) + resp, err := http.PostForm(c.addr+reverseFuncMap["Status"], values) if err != nil { return nil, err } @@ -416,11 +418,11 @@ func (c *ClientHTTP) Status() (*core.ResponseStatus, error) { return response.Result, nil } -func (c *ClientJSON) BlockchainInfo(minHeight uint) (*core.ResponseBlockchainInfo, error) { +func (c *ClientJSON) BlockchainInfo(minHeight uint, maxHeight uint) (*core.ResponseBlockchainInfo, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "blockchain_info", - Params: []interface{}{minHeight}, + Method: reverseFuncMap["BlockchainInfo"], + Params: []interface{}{minHeight, maxHeight}, Id: 0, } body, err := c.RequestResponse(request) @@ -446,7 +448,7 @@ func (c *ClientJSON) BlockchainInfo(minHeight uint) (*core.ResponseBlockchainInf func (c *ClientJSON) BroadcastTx(tx types.Tx) (*core.ResponseBroadcastTx, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "broadcast_tx", + Method: reverseFuncMap["BroadcastTx"], Params: []interface{}{tx}, Id: 0, } @@ -470,11 +472,11 @@ func (c *ClientJSON) BroadcastTx(tx types.Tx) (*core.ResponseBroadcastTx, error) return response.Result, nil } -func (c *ClientJSON) Call(address []byte) (*core.ResponseCall, error) { +func (c *ClientJSON) Call(address []byte, data []byte) (*core.ResponseCall, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "call", - Params: []interface{}{address}, + Method: reverseFuncMap["Call"], + Params: []interface{}{address, data}, Id: 0, } body, err := c.RequestResponse(request) @@ -500,7 +502,7 @@ func (c *ClientJSON) Call(address []byte) (*core.ResponseCall, error) { func (c *ClientJSON) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "dump_storage", + Method: reverseFuncMap["DumpStorage"], Params: []interface{}{addr}, Id: 0, } @@ -527,7 +529,7 @@ func (c *ClientJSON) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) func (c *ClientJSON) GenPrivAccount() (*core.ResponseGenPrivAccount, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "gen_priv_account", + Method: reverseFuncMap["GenPrivAccount"], Params: []interface{}{nil}, Id: 0, } @@ -554,7 +556,7 @@ func (c *ClientJSON) GenPrivAccount() (*core.ResponseGenPrivAccount, error) { func (c *ClientJSON) GetAccount(address []byte) (*core.ResponseGetAccount, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "get_account", + Method: reverseFuncMap["GetAccount"], Params: []interface{}{address}, Id: 0, } @@ -581,7 +583,7 @@ func (c *ClientJSON) GetAccount(address []byte) (*core.ResponseGetAccount, error func (c *ClientJSON) GetBlock(height uint) (*core.ResponseGetBlock, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "get_block", + Method: reverseFuncMap["GetBlock"], Params: []interface{}{height}, Id: 0, } @@ -605,11 +607,11 @@ func (c *ClientJSON) GetBlock(height uint) (*core.ResponseGetBlock, error) { return response.Result, nil } -func (c *ClientJSON) GetStorage(address []byte) (*core.ResponseGetStorage, error) { +func (c *ClientJSON) GetStorage(address []byte, storage []byte) (*core.ResponseGetStorage, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "get_storage", - Params: []interface{}{address}, + Method: reverseFuncMap["GetStorage"], + Params: []interface{}{address, storage}, Id: 0, } body, err := c.RequestResponse(request) @@ -635,7 +637,7 @@ func (c *ClientJSON) GetStorage(address []byte) (*core.ResponseGetStorage, error func (c *ClientJSON) ListAccounts() (*core.ResponseListAccounts, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "list_accounts", + Method: reverseFuncMap["ListAccounts"], Params: []interface{}{nil}, Id: 0, } @@ -662,7 +664,7 @@ func (c *ClientJSON) ListAccounts() (*core.ResponseListAccounts, error) { func (c *ClientJSON) ListValidators() (*core.ResponseListValidators, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "list_validators", + Method: reverseFuncMap["ListValidators"], Params: []interface{}{nil}, Id: 0, } @@ -689,7 +691,7 @@ func (c *ClientJSON) ListValidators() (*core.ResponseListValidators, error) { func (c *ClientJSON) NetInfo() (*core.ResponseNetInfo, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "net_info", + Method: reverseFuncMap["NetInfo"], Params: []interface{}{nil}, Id: 0, } @@ -716,7 +718,7 @@ func (c *ClientJSON) NetInfo() (*core.ResponseNetInfo, error) { func (c *ClientJSON) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*core.ResponseSignTx, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "sign_tx", + Method: reverseFuncMap["SignTx"], Params: []interface{}{tx, privAccounts}, Id: 0, } @@ -743,7 +745,7 @@ func (c *ClientJSON) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (* func (c *ClientJSON) Status() (*core.ResponseStatus, error) { request := RPCRequest{ JSONRPC: "2.0", - Method: "status", + Method: reverseFuncMap["Status"], Params: []interface{}{nil}, Id: 0, } diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go index 4234656b5..2cf2b6a14 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -15,20 +15,20 @@ func GetAccount(address []byte) (*ResponseGetAccount, error) { return &ResponseGetAccount{cache.GetAccount(address)}, nil } -func GetStorage(address, slot []byte) (*ResponseGetStorage, error) { +func GetStorage(address, storage []byte) (*ResponseGetStorage, error) { state := consensusState.GetState() account := state.GetAccount(address) if account == nil { return nil, fmt.Errorf("Unknown address: %X", address) } storageRoot := account.StorageRoot - storage := state.LoadStorage(storageRoot) + storageTree := state.LoadStorage(storageRoot) - _, value := storage.Get(RightPadWord256(slot).Bytes()) + _, value := storageTree.Get(RightPadWord256(storage).Bytes()) if value == nil { - return &ResponseGetStorage{slot, nil}, nil + return &ResponseGetStorage{storage, nil}, nil } - return &ResponseGetStorage{slot, value.([]byte)}, nil + return &ResponseGetStorage{storage, value.([]byte)}, nil } func ListAccounts() (*ResponseListAccounts, error) { diff --git a/rpc/handlers.go b/rpc/handlers.go index 69ead12c0..732164013 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -12,8 +12,14 @@ import ( "io/ioutil" "net/http" "reflect" + "runtime" + "strings" ) +// maps camel-case function names to lower case rpc version +// populated by calls to funcWrap +var reverseFuncMap = make(map[string]string) + // cache all type information about each function up front // (func, responseStruct, argNames) var funcMap = map[string]*FuncWrapper{ @@ -40,6 +46,16 @@ func initHandlers() { // JSONRPC endpoints http.HandleFunc("/", JSONRPCHandler) + + // fill the map from camelcase to lowercase + for name, f := range funcMap { + camelName := runtime.FuncForPC(f.f.Pointer()).Name() + spl := strings.Split(camelName, ".") + if len(spl) > 1 { + camelName = spl[len(spl)-1] + } + reverseFuncMap[camelName] = name + } } //------------------------------------- @@ -86,6 +102,10 @@ func funcReturnTypes(f interface{}) []reflect.Type { // jsonrpc calls grab the given method's function info and runs reflect.Call func JSONRPCHandler(w http.ResponseWriter, r *http.Request) { + if len(r.URL.Path) > 1 { + WriteRPCResponse(w, NewRPCResponse(nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path))) + return + } b, _ := ioutil.ReadAll(r.Body) var request RPCRequest err := json.Unmarshal(b, &request) diff --git a/rpc/test/http_rpc_test.go b/rpc/test/http_rpc_test.go index c49683eb2..61bafd7f9 100644 --- a/rpc/test/http_rpc_test.go +++ b/rpc/test/http_rpc_test.go @@ -4,67 +4,31 @@ import ( "bytes" "encoding/hex" "fmt" - "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/merkle" - "github.com/tendermint/tendermint/rpc/core" "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" - "io/ioutil" - "net/http" "testing" "time" ) func TestHTTPStatus(t *testing.T) { - resp, err := http.Get(requestAddr + "status") + client := clients["HTTP"] + resp, err := client.Status() if err != nil { t.Fatal(err) } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - var response struct { - Result core.ResponseStatus `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - binary.ReadJSON(&response, body, &err) - if err != nil { - t.Fatal(err) - } - result := response.Result - fmt.Println(">>>", result) + fmt.Println(">>>", resp) return } 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 response struct { - Result core.ResponseGenPrivAccount `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - binary.ReadJSON(&response, body, &err) + client := clients["HTTP"] + resp, err := client.GenPrivAccount() if err != nil { t.Fatal(err) } - fmt.Println(">>>", response) + fmt.Println(">>>", resp) } func TestHTTPGetAccount(t *testing.T) { @@ -153,14 +117,10 @@ func TestHTTPGetStorage(t *testing.T) { time.Sleep(time.Second * 20) mempoolCount -= 1 - v := getStorage(t, contractAddr, []byte{0x1}) + v := getStorage(t, "HTTP", contractAddr, []byte{0x1}) got := RightPadWord256(v) expected := RightPadWord256([]byte{0x5}) if got.Compare(expected) != 0 { t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), expected.Bytes()) } } - -/*tx.Inputs[0].Signature = mint.priv.PrivKey.Sign(account.SignBytes(tx)) -err = mint.MempoolReactor.BroadcastTx(tx) -return hex.EncodeToString(merkle.HashFromBinary(tx)), err*/ diff --git a/rpc/test/test.go b/rpc/test/test.go index 0b31525da..f8253f6ca 100644 --- a/rpc/test/test.go +++ b/rpc/test/test.go @@ -30,6 +30,11 @@ var ( userAddr = "D7DFF9806078899C8DA3FE3633CC0BF3C6C2B1BB" userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" userPub = "2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" + + clients = map[string]rpc.Client{ + "JSONRPC": rpc.NewClient(requestAddr, "JSONRPC"), + "HTTP": rpc.NewClient(requestAddr, "HTTP"), + } ) func decodeHex(hexStr string) []byte { @@ -40,6 +45,8 @@ func decodeHex(hexStr string) []byte { return bytes } + +// create a new node and sleep forever func newNode(ready chan struct{}) { // Create & start node node = daemon.NewNode() @@ -56,6 +63,7 @@ func newNode(ready chan struct{}) { <-ch } +// initialize config and create new node func init() { rootDir := ".tendermint" config.Init(rootDir) @@ -84,64 +92,10 @@ func init() { <-ready } -func getAccount(t *testing.T, typ string, addr []byte) *account.Account { - var client rpc.Client - switch typ { - case "JSONRPC": - client = rpc.NewClient(requestAddr, "JSONRPC") - case "HTTP": - client = rpc.NewClient(requestAddr, "HTTP") - } - ac, err := client.GetAccount(addr) - if err != nil { - t.Fatal(err) - } - return ac.Account -} - -/* -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: []interface{}{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": {"\"" + (hex.EncodeToString(addr)) + "\""}}) - } - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - var response struct { - Result core.ResponseGetAccount `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - binary.ReadJSON(&response, body, &err) - if err != nil { - t.Fatal(err) - } - return response.Result.Account -}*/ +//------------------------------------------------------------------------------- +// make transactions +// make a send tx (uses get account to figure out the nonce) func makeSendTx(t *testing.T, typ string, from, to []byte, amt uint64) *types.SendTx { acc := getAccount(t, typ, from) nonce := 0 @@ -172,6 +126,7 @@ func makeSendTx(t *testing.T, typ string, from, to []byte, amt uint64) *types.Se return tx } +// make a call tx (uses get account to figure out the nonce) func makeCallTx(t *testing.T, typ string, from, to, data []byte, amt, gaslim, fee uint64) *types.CallTx { acc := getAccount(t, typ, from) nonce := 0 @@ -199,22 +154,21 @@ func makeCallTx(t *testing.T, typ string, from, to, data []byte, amt, gaslim, fe return tx } -func requestResponse(t *testing.T, method string, values url.Values, response 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) - } - binary.ReadJSON(response, body, &err) +// make transactions +//------------------------------------------------------------------------------- +// rpc call wrappers + +// get the account +func getAccount(t *testing.T, typ string, addr []byte) *account.Account { + client := clients[typ] + ac, err := client.GetAccount(addr) if err != nil { t.Fatal(err) } + return ac.Account } +// make and sign transaction func signTx(t *testing.T, typ string, fromAddr, toAddr, data []byte, key [64]byte, amt, gaslim, fee uint64) (types.Tx, *account.PrivAccount) { var tx types.Tx if data == nil { @@ -223,64 +177,39 @@ func signTx(t *testing.T, typ string, fromAddr, toAddr, data []byte, key [64]byt tx = makeCallTx(t, typ, fromAddr, toAddr, data, amt, gaslim, fee) } - 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) + + client := clients[typ] + resp, err := client.SignTx(tx, []*account.PrivAccount{privAcc}) if err != nil { t.Fatal(err) } - - var response struct { - Result core.ResponseSignTx `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - requestResponse(t, "unsafe/sign_tx", url.Values{"tx": {string(b)}, "privAccounts": {string(w.Bytes())}}, &response) - if response.Error != "" { - t.Fatal(response.Error) - } - result := response.Result - return result.Tx, privAcc + return resp.Tx, privAcc } +// create, sign, and broadcast a transaction func broadcastTx(t *testing.T, typ string, fromAddr, toAddr, data []byte, key [64]byte, amt, gaslim, fee uint64) (types.Tx, core.Receipt) { tx, _ := signTx(t, typ, fromAddr, toAddr, data, key, amt, gaslim, fee) - - n, w := new(int64), new(bytes.Buffer) - var err error - binary.WriteJSON(tx, w, n, &err) + client := clients[typ] + resp, err := client.BroadcastTx(tx) if err != nil { t.Fatal(err) } - b := w.Bytes() - - var response struct { - Result core.ResponseBroadcastTx `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &response) - if response.Error != "" { - t.Fatal(response.Error) - } - return tx, response.Result.Receipt + return tx, resp.Receipt } +// dump all storage for an account. currently unused func dumpStorage(t *testing.T, addr []byte) core.ResponseDumpStorage { - addrString := "\"" + hex.EncodeToString(addr) + "\"" + client := clients["HTTP"] + resp, err := client.DumpStorage(addr) + if err != nil { + t.Fatal(err) + } + return *resp + /*addrString := "\"" + hex.EncodeToString(addr) + "\"" var response struct { Result core.ResponseDumpStorage `json:"result"` Error string `json:"error"` @@ -290,26 +219,21 @@ func dumpStorage(t *testing.T, addr []byte) core.ResponseDumpStorage { requestResponse(t, "dump_storage", url.Values{"address": {addrString}}, &response) if response.Error != "" { t.Fatal(response.Error) - } - return response.Result + }*/ } -func getStorage(t *testing.T, addr, slot []byte) []byte { - addrString := "\"" + hex.EncodeToString(addr) + "\"" - slotString := "\"" + hex.EncodeToString(slot) + "\"" - var response struct { - Result core.ResponseGetStorage `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - requestResponse(t, "get_storage", url.Values{"address": {addrString}, "storage": {slotString}}, &response) - if response.Error != "" { - t.Fatal(response.Error) +func getStorage(t *testing.T, typ string, addr, slot []byte) []byte { + client := clients[typ] + resp, err := client.GetStorage(addr, slot) + if err != nil { + t.Fatal(err) } - return response.Result.Value + return resp.Value } +//-------------------------------------------------------------------------------- +// utility verification function + 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!") @@ -328,3 +252,21 @@ func checkTx(t *testing.T, fromAddr []byte, priv *account.PrivAccount, tx *types t.Fatal(types.ErrTxInvalidSignature) } } + +//-------------------------------------------------------------------------------- + +func requestResponse(t *testing.T, method string, values url.Values, response 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) + } + binary.ReadJSON(response, body, &err) + if err != nil { + t.Fatal(err) + } +} From 2b3bedead87fc60d83a7d5e2240df539594e666d Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 2 Apr 2015 14:30:30 -0700 Subject: [PATCH 2/8] rpc: websockets --- rpc/handlers.go | 191 +++++++++++++++++++++++++++++++++++++++++---- rpc/http_server.go | 6 +- 2 files changed, 181 insertions(+), 16 deletions(-) diff --git a/rpc/handlers.go b/rpc/handlers.go index 732164013..9fc1f7874 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -5,21 +5,21 @@ TODO: support Call && GetStorage. */ import ( + "bytes" "encoding/json" "fmt" "github.com/tendermint/tendermint/binary" + "github.com/tendermint/tendermint/events" "github.com/tendermint/tendermint/rpc/core" + "golang.org/x/net/websocket" "io/ioutil" "net/http" "reflect" "runtime" "strings" + "time" ) -// maps camel-case function names to lower case rpc version -// populated by calls to funcWrap -var reverseFuncMap = make(map[string]string) - // cache all type information about each function up front // (func, responseStruct, argNames) var funcMap = map[string]*FuncWrapper{ @@ -38,24 +38,36 @@ var funcMap = map[string]*FuncWrapper{ "unsafe/sign_tx": funcWrap(core.SignTx, []string{"tx", "privAccounts"}), } -func initHandlers() { - // HTTP endpoints - for funcName, funcInfo := range funcMap { - http.HandleFunc("/"+funcName, toHTTPHandler(funcInfo)) - } - - // JSONRPC endpoints - http.HandleFunc("/", JSONRPCHandler) +// maps camel-case function names to lower case rpc version +// populated by calls to funcWrap +var reverseFuncMap = fillReverseFuncMap() - // fill the map from camelcase to lowercase +// fill the map from camelcase to lowercase +func fillReverseFuncMap() map[string]string { + fMap := make(map[string]string) for name, f := range funcMap { camelName := runtime.FuncForPC(f.f.Pointer()).Name() spl := strings.Split(camelName, ".") if len(spl) > 1 { camelName = spl[len(spl)-1] } - reverseFuncMap[camelName] = name + fMap[camelName] = name + } + return fMap +} + +func initHandlers(ew *events.EventSwitch) { + // HTTP endpoints + for funcName, funcInfo := range funcMap { + http.HandleFunc("/"+funcName, toHTTPHandler(funcInfo)) } + + // JSONRPC endpoints + http.HandleFunc("/", JSONRPCHandler) + + w := NewWebsocketManager(ew) + // websocket endpoint + http.Handle("/events", websocket.Handler(w.eventsHandler)) } //------------------------------------- @@ -212,6 +224,157 @@ func _jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) { // rpc.http //----------------------------------------------------------------------------- +// rpc.websocket + +// main manager for all websocket connections +// holds the event switch +type WebsocketManager struct { + ew *events.EventSwitch + cons map[string]*Connection +} + +func NewWebsocketManager(ew *events.EventSwitch) *WebsocketManager { + return &WebsocketManager{ + ew: ew, + cons: make(map[string]*Connection), + } +} + +func (w *WebsocketManager) eventsHandler(con *websocket.Conn) { + // register connection + c := NewConnection(con) + w.cons[con.RemoteAddr().String()] = c + + // read subscriptions/unsubscriptions to events + go w.read(c) + // write responses + go w.write(c) +} + +const ( + WsConnectionReaperSeconds = 5 + MaxFailedSendsSeconds = 10 + WriteChanBuffer = 10 +) + +// read from the socket and subscribe to or unsubscribe from events +func (w *WebsocketManager) read(con *Connection) { + reaper := time.Tick(time.Second * WsConnectionReaperSeconds) + for { + select { + case <-reaper: + if con.failedSends > MaxFailedSendsSeconds { + // sending has failed too many times. + // kill the connection + con.quitChan <- struct{}{} + } + default: + var in []byte + if err := websocket.Message.Receive(con.wsCon, &in); err != nil { + // an error reading the connection, + // so kill the connection + con.quitChan <- struct{}{} + } + var req WsRequest + err := json.Unmarshal(in, &req) + if err != nil { + errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error()) + con.writeChan <- WsResponse{Error: errStr} + } + switch req.Type { + case "subscribe": + w.ew.AddListenerForEvent(con.id, req.Event, func(msg interface{}) { + resp := WsResponse{ + Event: req.Event, + Data: msg, + } + select { + case con.writeChan <- resp: + // yay + con.failedSends = 0 + default: + // channel is full + // if this happens too many times, + // close connection + con.failedSends += 1 + } + }) + case "unsubscribe": + if req.Event != "" { + w.ew.RemoveListenerForEvent(req.Event, con.id) + } else { + w.ew.RemoveListener(con.id) + } + default: + con.writeChan <- WsResponse{Error: "Unknown request type: " + req.Type} + } + + } + } +} + +// receives on a write channel and writes out to the socket +func (w *WebsocketManager) write(con *Connection) { + n, err := new(int64), new(error) + for { + select { + case msg := <-con.writeChan: + buf := new(bytes.Buffer) + binary.WriteJSON(msg, buf, n, err) + if *err != nil { + log.Error("Failed to write JSON WsResponse", "error", err) + } else { + websocket.Message.Send(con.wsCon, buf.Bytes()) + } + case <-con.quitChan: + close(con.quitChan) + con.Close() + return + } + } +} + +// a single websocket connection +// contains the listeners id +type Connection struct { + id string + wsCon *websocket.Conn + writeChan chan WsResponse + quitChan chan struct{} + failedSends uint +} + +// for requests coming in +type WsRequest struct { + Type string // subscribe or unsubscribe + Event string +} + +// for responses going out +type WsResponse struct { + Event string + Data interface{} + Error string +} + +// new websocket connection wrapper +func NewConnection(con *websocket.Conn) *Connection { + return &Connection{ + id: con.RemoteAddr().String(), + wsCon: con, + writeChan: make(chan WsResponse, WriteChanBuffer), // buffered. we keep track when its full + } +} + +// close the channel +// should only be called by firing on c.quitChan +func (c *Connection) Close() { + close(c.writeChan) + c.wsCon.Close() +} + +// rpc.websocket +//----------------------------------------------------------------------------- // returns is Response struct and error. If error is not nil, return it func unreflectResponse(returns []reflect.Value) (interface{}, error) { diff --git a/rpc/http_server.go b/rpc/http_server.go index 47770f3d8..7de0daf81 100644 --- a/rpc/http_server.go +++ b/rpc/http_server.go @@ -12,15 +12,17 @@ import ( "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/events" ) -func StartHTTPServer() { - initHandlers() +func StartHTTPServer(ew *events.EventSwitch) { + initHandlers(ew) log.Info(Fmt("Starting RPC HTTP server on %s", config.App().GetString("RPC.HTTP.ListenAddr"))) go func() { log.Crit("RPC HTTPServer stopped", "result", http.ListenAndServe(config.App().GetString("RPC.HTTP.ListenAddr"), RecoverAndLogHandler(http.DefaultServeMux))) }() + } func WriteRPCResponse(w http.ResponseWriter, res RPCResponse) { From 474bf31400ee93909a3159fd3b40a4c8e1aa4995 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 2 Apr 2015 17:35:27 -0700 Subject: [PATCH 3/8] events: integrate event switch into services via Eventable interface --- blockchain/reactor.go | 9 ++++- consensus/reactor.go | 8 +++++ daemon/daemon.go | 19 +++++++++- events/events.go | 6 ++++ mempool/reactor.go | 8 +++++ p2p/pex_reactor.go | 8 +++++ rpc/handlers.go | 83 +++++++++++++++++++++++-------------------- 7 files changed, 100 insertions(+), 41 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 7e776ab07..f389f3c91 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -9,6 +9,7 @@ import ( "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" + "github.com/tendermint/tendermint/events" "github.com/tendermint/tendermint/p2p" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -19,7 +20,6 @@ const ( defaultChannelCapacity = 100 defaultSleepIntervalMS = 500 trySyncIntervalMS = 100 - // stop syncing when last block's time is // within this much of the system time. stopSyncingDurationMinutes = 10 @@ -41,6 +41,8 @@ type BlockchainReactor struct { lastBlock *types.Block quit chan struct{} running uint32 + + evsw *events.EventSwitch } func NewBlockchainReactor(state *sm.State, store *BlockStore, sync bool) *BlockchainReactor { @@ -239,6 +241,11 @@ func (bcR *BlockchainReactor) BroadcastStatus() error { return nil } +// implements events.Eventable +func (bcR *BlockchainReactor) AddEventSwitch(evsw *events.EventSwitch) { + bcR.evsw = evsw +} + //----------------------------------------------------------------------------- // Messages diff --git a/consensus/reactor.go b/consensus/reactor.go index a3028b6d5..da358e594 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -12,6 +12,7 @@ import ( bc "github.com/tendermint/tendermint/blockchain" . "github.com/tendermint/tendermint/common" . "github.com/tendermint/tendermint/consensus/types" + "github.com/tendermint/tendermint/events" "github.com/tendermint/tendermint/p2p" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -38,6 +39,8 @@ type ConsensusReactor struct { blockStore *bc.BlockStore conS *ConsensusState + + evsw *events.EventSwitch } func NewConsensusReactor(consensusState *ConsensusState, blockStore *bc.BlockStore) *ConsensusReactor { @@ -230,6 +233,11 @@ func (conR *ConsensusReactor) ResetToState(state *sm.State) { conR.conS.updateToState(state, false) } +// implements events.Eventable +func (conR *ConsensusReactor) AddEventSwitch(evsw *events.EventSwitch) { + conR.evsw = evsw +} + //-------------------------------------- func makeRoundStepMessages(rs *RoundState) (nrsMsg *NewRoundStepMessage, csMsg *CommitStepMessage) { diff --git a/daemon/daemon.go b/daemon/daemon.go index 419c3786f..ceaafaa5c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -9,6 +9,7 @@ import ( "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/consensus" dbm "github.com/tendermint/tendermint/db" + "github.com/tendermint/tendermint/events" mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/rpc" @@ -18,6 +19,7 @@ import ( type Node struct { sw *p2p.Switch + evsw *events.EventSwitch book *p2p.AddrBook blockStore *bc.BlockStore pexReactor *p2p.PEXReactor @@ -50,6 +52,9 @@ func NewNode() *Node { log.Info("No PrivValidator found", "file", config.App().GetString("PrivValidatorFile")) } + eventSwitch := new(events.EventSwitch) + eventSwitch.Start() + // Get PEXReactor book := p2p.NewAddrBook(config.App().GetString("AddrBookFile")) pexReactor := p2p.NewPEXReactor(book) @@ -75,8 +80,13 @@ func NewNode() *Node { sw.AddReactor("BLOCKCHAIN", bcReactor) sw.AddReactor("CONSENSUS", consensusReactor) + // add the event switch to all services + // they should all satisfy events.Eventable + AddEventSwitch(eventSwitch, pexReactor, bcReactor, mempoolReactor, consensusReactor) + return &Node{ sw: sw, + evsw: eventSwitch, book: book, blockStore: blockStore, pexReactor: pexReactor, @@ -105,6 +115,13 @@ func (n *Node) Stop() { n.book.Stop() } +// Add the event switch to reactors, mempool, etc. +func AddEventSwitch(evsw *events.EventSwitch, eventables ...events.Eventable) { + for _, e := range eventables { + e.AddEventSwitch(evsw) + } +} + // Add a Listener to accept inbound peer connections. func (n *Node) AddListener(l p2p.Listener) { log.Info(Fmt("Added %v", l)) @@ -130,7 +147,7 @@ func (n *Node) StartRPC() { core.SetConsensusState(n.consensusState) core.SetMempoolReactor(n.mempoolReactor) core.SetSwitch(n.sw) - rpc.StartHTTPServer() + rpc.StartHTTPServer(n.evsw) } func (n *Node) Switch() *p2p.Switch { diff --git a/events/events.go b/events/events.go index 05c6f663f..73d1935fe 100644 --- a/events/events.go +++ b/events/events.go @@ -5,6 +5,12 @@ import ( "sync/atomic" ) +// reactors and other modules should export +// this interface to become eventable +type Eventable interface { + AddEventSwitch(*EventSwitch) +} + type EventSwitch struct { mtx sync.RWMutex eventCells map[string]*eventCell diff --git a/mempool/reactor.go b/mempool/reactor.go index e16cf9332..f6d66f67f 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -6,6 +6,7 @@ import ( "sync/atomic" "github.com/tendermint/tendermint/binary" + "github.com/tendermint/tendermint/events" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/types" ) @@ -22,6 +23,8 @@ type MempoolReactor struct { stopped uint32 Mempool *Mempool + + evsw *events.EventSwitch } func NewMempoolReactor(mempool *Mempool) *MempoolReactor { @@ -110,6 +113,11 @@ func (memR *MempoolReactor) BroadcastTx(tx types.Tx) error { return nil } +// implements events.Eventable +func (memR *MempoolReactor) AddEventSwitch(evsw *events.EventSwitch) { + memR.evsw = evsw +} + //----------------------------------------------------------------------------- // Messages diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 6af070589..2c0033958 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -9,6 +9,7 @@ import ( "github.com/tendermint/tendermint/binary" . "github.com/tendermint/tendermint/common" + "github.com/tendermint/tendermint/events" ) var pexErrInvalidMessage = errors.New("Invalid PEX message") @@ -31,6 +32,8 @@ type PEXReactor struct { stopped uint32 book *AddrBook + + evsw *events.EventSwitch } func NewPEXReactor(book *AddrBook) *PEXReactor { @@ -207,6 +210,11 @@ func (pexR *PEXReactor) ensurePeers() { } } +// implements events.Eventable +func (pexR *PEXReactor) AddEventSwitch(evsw *events.EventSwitch) { + pexR.evsw = evsw +} + //----------------------------------------------------------------------------- // Messages diff --git a/rpc/handlers.go b/rpc/handlers.go index 9fc1f7874..fc6a501df 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -226,6 +226,45 @@ func _jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) { //----------------------------------------------------------------------------- // rpc.websocket +// for requests coming in +type WsRequest struct { + Type string // subscribe or unsubscribe + Event string +} + +// for responses going out +type WsResponse struct { + Event string + Data interface{} + Error string +} + +// a single websocket connection +// contains the listeners id +type Connection struct { + id string + wsCon *websocket.Conn + writeChan chan WsResponse + quitChan chan struct{} + failedSends uint +} + +// new websocket connection wrapper +func NewConnection(con *websocket.Conn) *Connection { + return &Connection{ + id: con.RemoteAddr().String(), + wsCon: con, + writeChan: make(chan WsResponse, WriteChanBuffer), // buffered. we keep track when its full + } +} + +// close the connection +func (c *Connection) Close() { + c.wsCon.Close() + close(c.writeChan) + close(c.quitChan) +} + // main manager for all websocket connections // holds the event switch type WebsocketManager struct { @@ -327,50 +366,16 @@ func (w *WebsocketManager) write(con *Connection) { websocket.Message.Send(con.wsCon, buf.Bytes()) } case <-con.quitChan: - close(con.quitChan) - con.Close() + w.closeConn(con) return } } } -// a single websocket connection -// contains the listeners id -type Connection struct { - id string - wsCon *websocket.Conn - writeChan chan WsResponse - quitChan chan struct{} - failedSends uint -} - -// for requests coming in -type WsRequest struct { - Type string // subscribe or unsubscribe - Event string -} - -// for responses going out -type WsResponse struct { - Event string - Data interface{} - Error string -} - -// new websocket connection wrapper -func NewConnection(con *websocket.Conn) *Connection { - return &Connection{ - id: con.RemoteAddr().String(), - wsCon: con, - writeChan: make(chan WsResponse, WriteChanBuffer), // buffered. we keep track when its full - } -} - -// close the channel -// should only be called by firing on c.quitChan -func (c *Connection) Close() { - close(c.writeChan) - c.wsCon.Close() +// close a connection and delete from manager +func (w *WebsocketManager) closeConn(con *Connection) { + con.Close() + delete(w.cons, con.id) } // rpc.websocket From 3bedcbf94d2b9b0232bb03729bee6c28bb44932d Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 2 Apr 2015 18:11:37 -0700 Subject: [PATCH 4/8] rpc: tests cleanup, use client lib for JSONRPC testing too --- rpc/client_methods.go | 20 +-- rpc/test/client_rpc_test.go | 59 ++++++++ rpc/test/{test.go => helpers.go} | 34 +---- rpc/test/json_rpc_test.go | 177 ------------------------ rpc/test/{http_rpc_test.go => tests.go} | 48 ++++--- 5 files changed, 98 insertions(+), 240 deletions(-) create mode 100644 rpc/test/client_rpc_test.go rename rpc/test/{test.go => helpers.go} (86%) delete mode 100644 rpc/test/json_rpc_test.go rename rpc/test/{http_rpc_test.go => tests.go} (68%) diff --git a/rpc/client_methods.go b/rpc/client_methods.go index 872e723e8..e290e8ae9 100644 --- a/rpc/client_methods.go +++ b/rpc/client_methods.go @@ -149,7 +149,7 @@ func (c *ClientHTTP) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) } func (c *ClientHTTP) GenPrivAccount() (*core.ResponseGenPrivAccount, error) { - values, err := argsToURLValues(nil, nil) + values, err := argsToURLValues(nil) if err != nil { return nil, err } @@ -269,7 +269,7 @@ func (c *ClientHTTP) GetStorage(address []byte, storage []byte) (*core.ResponseG } func (c *ClientHTTP) ListAccounts() (*core.ResponseListAccounts, error) { - values, err := argsToURLValues(nil, nil) + values, err := argsToURLValues(nil) if err != nil { return nil, err } @@ -299,7 +299,7 @@ func (c *ClientHTTP) ListAccounts() (*core.ResponseListAccounts, error) { } func (c *ClientHTTP) ListValidators() (*core.ResponseListValidators, error) { - values, err := argsToURLValues(nil, nil) + values, err := argsToURLValues(nil) if err != nil { return nil, err } @@ -329,7 +329,7 @@ func (c *ClientHTTP) ListValidators() (*core.ResponseListValidators, error) { } func (c *ClientHTTP) NetInfo() (*core.ResponseNetInfo, error) { - values, err := argsToURLValues(nil, nil) + values, err := argsToURLValues(nil) if err != nil { return nil, err } @@ -389,7 +389,7 @@ func (c *ClientHTTP) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (* } func (c *ClientHTTP) Status() (*core.ResponseStatus, error) { - values, err := argsToURLValues(nil, nil) + values, err := argsToURLValues(nil) if err != nil { return nil, err } @@ -530,7 +530,7 @@ func (c *ClientJSON) GenPrivAccount() (*core.ResponseGenPrivAccount, error) { request := RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["GenPrivAccount"], - Params: []interface{}{nil}, + Params: []interface{}{}, Id: 0, } body, err := c.RequestResponse(request) @@ -638,7 +638,7 @@ func (c *ClientJSON) ListAccounts() (*core.ResponseListAccounts, error) { request := RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["ListAccounts"], - Params: []interface{}{nil}, + Params: []interface{}{}, Id: 0, } body, err := c.RequestResponse(request) @@ -665,7 +665,7 @@ func (c *ClientJSON) ListValidators() (*core.ResponseListValidators, error) { request := RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["ListValidators"], - Params: []interface{}{nil}, + Params: []interface{}{}, Id: 0, } body, err := c.RequestResponse(request) @@ -692,7 +692,7 @@ func (c *ClientJSON) NetInfo() (*core.ResponseNetInfo, error) { request := RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["NetInfo"], - Params: []interface{}{nil}, + Params: []interface{}{}, Id: 0, } body, err := c.RequestResponse(request) @@ -746,7 +746,7 @@ func (c *ClientJSON) Status() (*core.ResponseStatus, error) { request := RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["Status"], - Params: []interface{}{nil}, + Params: []interface{}{}, Id: 0, } body, err := c.RequestResponse(request) diff --git a/rpc/test/client_rpc_test.go b/rpc/test/client_rpc_test.go new file mode 100644 index 000000000..e13f91e03 --- /dev/null +++ b/rpc/test/client_rpc_test.go @@ -0,0 +1,59 @@ +package rpc + +import ( + "testing" +) + +//-------------------------------------------------------------------------------- +// Test the HTTP client + +func TestHTTPStatus(t *testing.T) { + testStatus(t, "HTTP") +} + +func TestHTTPGenPriv(t *testing.T) { + testGenPriv(t, "HTTP") +} + +func TestHTTPGetAccount(t *testing.T) { + testGetAccount(t, "HTTP") +} + +func TestHTTPSignedTx(t *testing.T) { + testSignedTx(t, "HTTP") +} + +func TestHTTPBroadcastTx(t *testing.T) { + testBroadcastTx(t, "HTTP") +} + +func TestHTTPGetStorage(t *testing.T) { + testGetStorage(t, "HTTP") +} + +//-------------------------------------------------------------------------------- +// Test the JSONRPC client + +func TestJSONStatus(t *testing.T) { + testStatus(t, "JSONRPC") +} + +func TestJSONGenPriv(t *testing.T) { + testGenPriv(t, "JSONRPC") +} + +func TestJSONGetAccount(t *testing.T) { + testGetAccount(t, "JSONRPC") +} + +func TestJSONSignedTx(t *testing.T) { + testSignedTx(t, "JSONRPC") +} + +func TestJSONBroadcastTx(t *testing.T) { + testBroadcastTx(t, "JSONRPC") +} + +func TestJSONGetStorage(t *testing.T) { + testGetStorage(t, "JSONRPC") +} diff --git a/rpc/test/test.go b/rpc/test/helpers.go similarity index 86% rename from rpc/test/test.go rename to rpc/test/helpers.go index f8253f6ca..43eb7fb67 100644 --- a/rpc/test/test.go +++ b/rpc/test/helpers.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/hex" "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" @@ -13,12 +12,10 @@ import ( "github.com/tendermint/tendermint/rpc/core" "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" - "io/ioutil" - "net/http" - "net/url" "testing" ) +// global variables for use across all tests var ( rpcAddr = "127.0.0.1:8089" requestAddr = "http://" + rpcAddr + "/" @@ -209,17 +206,6 @@ func dumpStorage(t *testing.T, addr []byte) core.ResponseDumpStorage { t.Fatal(err) } return *resp - /*addrString := "\"" + hex.EncodeToString(addr) + "\"" - var response struct { - Result core.ResponseDumpStorage `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - requestResponse(t, "dump_storage", url.Values{"address": {addrString}}, &response) - if response.Error != "" { - t.Fatal(response.Error) - }*/ } func getStorage(t *testing.T, typ string, addr, slot []byte) []byte { @@ -252,21 +238,3 @@ func checkTx(t *testing.T, fromAddr []byte, priv *account.PrivAccount, tx *types t.Fatal(types.ErrTxInvalidSignature) } } - -//-------------------------------------------------------------------------------- - -func requestResponse(t *testing.T, method string, values url.Values, response 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) - } - binary.ReadJSON(response, body, &err) - if err != nil { - t.Fatal(err) - } -} diff --git a/rpc/test/json_rpc_test.go b/rpc/test/json_rpc_test.go deleted file mode 100644 index 8500bc21d..000000000 --- a/rpc/test/json_rpc_test.go +++ /dev/null @@ -1,177 +0,0 @@ -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.RPCRequest{ - JSONRPC: "2.0", - Method: "status", - Params: []interface{}{}, - 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) - } - - var response struct { - Result core.ResponseStatus `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - binary.ReadJSON(&response, body, &err) - if err != nil { - t.Fatal(err) - } - if response.Result.Network != config.App().GetString("Network") { - t.Fatal(fmt.Errorf("Network mismatch: got %s expected %s", - response.Result.Network, config.App().Get("Network"))) - } -} - -func TestJSONGenPriv(t *testing.T) { - s := rpc.RPCRequest{ - JSONRPC: "2.0", - Method: "unsafe/gen_priv_account", - Params: []interface{}{}, - 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 response struct { - Result core.ResponseGenPrivAccount `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - binary.ReadJSON(&response, body, &err) - if err != nil { - t.Fatal(err) - } - if len(response.Result.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, nil, byteKey, amt, 0, 0) - checkTx(t, byteAddr, priv, tx.(*types.SendTx)) - - 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, nil, byteKey, amt, 0, 0) - checkTx(t, byteAddr, priv, tx.(*types.SendTx)) - - 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, nil, byteKey, amt, 0, 0) - checkTx(t, byteAddr, priv, tx.(*types.SendTx)) -} - -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, nil, byteKey, amt, 0, 0) - checkTx(t, byteAddr, priv, tx.(*types.SendTx)) - - 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 response struct { - Result core.ResponseBroadcastTx `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` - } - requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &response) - if response.Error != "" { - t.Fatal(response.Error) - } - receipt := response.Result.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*/ diff --git a/rpc/test/http_rpc_test.go b/rpc/test/tests.go similarity index 68% rename from rpc/test/http_rpc_test.go rename to rpc/test/tests.go index 61bafd7f9..1b1ebc978 100644 --- a/rpc/test/http_rpc_test.go +++ b/rpc/test/tests.go @@ -5,45 +5,50 @@ import ( "encoding/hex" "fmt" . "github.com/tendermint/tendermint/common" - "github.com/tendermint/tendermint/merkle" + "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" "testing" "time" ) -func TestHTTPStatus(t *testing.T) { - client := clients["HTTP"] +func testStatus(t *testing.T, typ string) { + client := clients[typ] resp, err := client.Status() if err != nil { t.Fatal(err) } fmt.Println(">>>", resp) - return + if resp.Network != config.App().GetString("Network") { + t.Fatal(fmt.Errorf("Network mismatch: got %s expected %s", + resp.Network, config.App().Get("Network"))) + } } -func TestHTTPGenPriv(t *testing.T) { - client := clients["HTTP"] +func testGenPriv(t *testing.T, typ string) { + client := clients[typ] resp, err := client.GenPrivAccount() if err != nil { t.Fatal(err) } fmt.Println(">>>", resp) + if len(resp.PrivAccount.Address) == 0 { + t.Fatal("Failed to generate an address") + } } -func TestHTTPGetAccount(t *testing.T) { +func testGetAccount(t *testing.T, typ string) { byteAddr, _ := hex.DecodeString(userAddr) - acc := getAccount(t, "HTTP", byteAddr) + acc := getAccount(t, typ, byteAddr) if acc == nil { t.Fatalf("Account was nil") } 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) { +func testSignedTx(t *testing.T, typ string) { byteAddr, _ := hex.DecodeString(userAddr) var byteKey [64]byte oh, _ := hex.DecodeString(userPriv) @@ -51,19 +56,19 @@ func TestHTTPSignedTx(t *testing.T) { 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, nil, byteKey, amt, 0, 0) + tx, priv := signTx(t, typ, byteAddr, toAddr, nil, byteKey, amt, 0, 0) checkTx(t, byteAddr, priv, tx.(*types.SendTx)) 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, nil, byteKey, amt, 0, 0) + tx, priv = signTx(t, typ, byteAddr, toAddr, nil, byteKey, amt, 0, 0) checkTx(t, byteAddr, priv, tx.(*types.SendTx)) 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, nil, byteKey, amt, 0, 0) + tx, priv = signTx(t, typ, byteAddr, toAddr, nil, byteKey, amt, 0, 0) checkTx(t, byteAddr, priv, tx.(*types.SendTx)) } -func TestHTTPBroadcastTx(t *testing.T) { +func testBroadcastTx(t *testing.T, typ string) { byteAddr, _ := hex.DecodeString(userAddr) var byteKey [64]byte oh, _ := hex.DecodeString(userPriv) @@ -71,7 +76,7 @@ func TestHTTPBroadcastTx(t *testing.T) { 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, receipt := broadcastTx(t, "HTTP", byteAddr, toAddr, nil, byteKey, amt, 0, 0) + tx, receipt := broadcastTx(t, typ, byteAddr, toAddr, nil, byteKey, amt, 0, 0) if receipt.CreatesContract > 0 { t.Fatal("This tx does not create a contract") } @@ -85,13 +90,16 @@ func TestHTTPBroadcastTx(t *testing.T) { } tx2 := txs[mempoolCount].(*types.SendTx) mempoolCount += 1 - if bytes.Compare(merkle.HashFromBinary(tx), merkle.HashFromBinary(tx2)) != 0 { + n, err := new(int64), new(error) + buf1, buf2 := new(bytes.Buffer), new(bytes.Buffer) + tx.WriteSignBytes(buf1, n, err) + tx2.WriteSignBytes(buf2, n, err) + if bytes.Compare(buf1.Bytes(), buf2.Bytes()) != 0 { t.Fatal("inconsistent hashes for mempool tx and sent tx") } - } -func TestHTTPGetStorage(t *testing.T) { +func testGetStorage(t *testing.T, typ string) { priv := state.LoadPrivValidator(".tendermint/priv_validator.json") _ = priv //core.SetPrivValidator(priv) @@ -103,7 +111,7 @@ func TestHTTPGetStorage(t *testing.T) { amt := uint64(1100) code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} - _, receipt := broadcastTx(t, "HTTP", byteAddr, nil, code, byteKey, amt, 1000, 1000) + _, receipt := broadcastTx(t, typ, byteAddr, nil, code, byteKey, amt, 1000, 1000) if receipt.CreatesContract == 0 { t.Fatal("This tx creates a contract") } @@ -117,7 +125,7 @@ func TestHTTPGetStorage(t *testing.T) { time.Sleep(time.Second * 20) mempoolCount -= 1 - v := getStorage(t, "HTTP", contractAddr, []byte{0x1}) + v := getStorage(t, typ, contractAddr, []byte{0x1}) got := RightPadWord256(v) expected := RightPadWord256([]byte{0x5}) if got.Compare(expected) != 0 { From ade13daec1f14603c65d938471b9480f05f81cf6 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 2 Apr 2015 19:14:58 -0700 Subject: [PATCH 5/8] vm: fix errors not being returned --- vm/stack.go | 6 +++--- vm/vm.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vm/stack.go b/vm/stack.go index 6b74643ac..7219051fd 100644 --- a/vm/stack.go +++ b/vm/stack.go @@ -32,9 +32,9 @@ func (st *Stack) useGas(gasToUse uint64) { } func (st *Stack) setErr(err error) { - if *st.err != nil { - *st.err = err - } + //if *st.err != nil { + *st.err = err + //} } func (st *Stack) Push(d Word256) { diff --git a/vm/vm.go b/vm/vm.go index cbf68729d..e21a914f8 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -659,7 +659,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case RETURN: // 0xF3 offset, size := stack.Pop64(), stack.Pop64() ret, ok := subslice(memory, offset, size, false) - if !ok { + if !ok || err != nil { return nil, firstErr(err, ErrMemoryOutOfBounds) } dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret) From 5a2ff3d45b8ffec673b59dae9498f000ab052f86 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 2 Apr 2015 19:15:23 -0700 Subject: [PATCH 6/8] rpc: test CallCode and Call --- rpc/client_methods.go | 58 +++++++++++++++++++++++++++++++++ rpc/core/txs.go | 25 ++++++++++++++ rpc/handlers.go | 17 +++++----- rpc/test/client_rpc_test.go | 16 +++++++++ rpc/test/helpers.go | 25 ++++++++++++++ rpc/test/tests.go | 65 +++++++++++++++++++++++++++++++++++++ 6 files changed, 198 insertions(+), 8 deletions(-) diff --git a/rpc/client_methods.go b/rpc/client_methods.go index e290e8ae9..4f8a6b9f4 100644 --- a/rpc/client_methods.go +++ b/rpc/client_methods.go @@ -16,6 +16,7 @@ type Client interface { BlockchainInfo(minHeight uint, maxHeight uint) (*core.ResponseBlockchainInfo, error) BroadcastTx(tx types.Tx) (*core.ResponseBroadcastTx, error) Call(address []byte, data []byte) (*core.ResponseCall, error) + CallCode(code []byte, data []byte) (*core.ResponseCall, error) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) GenPrivAccount() (*core.ResponseGenPrivAccount, error) GetAccount(address []byte) (*core.ResponseGetAccount, error) @@ -118,6 +119,36 @@ func (c *ClientHTTP) Call(address []byte, data []byte) (*core.ResponseCall, erro return response.Result, nil } +func (c *ClientHTTP) CallCode(code []byte, data []byte) (*core.ResponseCall, error) { + values, err := argsToURLValues([]string{"code", "data"}, code, data) + if err != nil { + return nil, err + } + resp, err := http.PostForm(c.addr+reverseFuncMap["CallCode"], values) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var response struct { + Result *core.ResponseCall `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` + } + binary.ReadJSON(&response, body, &err) + if err != nil { + return nil, err + } + if response.Error != "" { + return nil, fmt.Errorf(response.Error) + } + return response.Result, nil +} + func (c *ClientHTTP) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) { values, err := argsToURLValues([]string{"addr"}, addr) if err != nil { @@ -499,6 +530,33 @@ func (c *ClientJSON) Call(address []byte, data []byte) (*core.ResponseCall, erro return response.Result, nil } +func (c *ClientJSON) CallCode(code []byte, data []byte) (*core.ResponseCall, error) { + request := RPCRequest{ + JSONRPC: "2.0", + Method: reverseFuncMap["CallCode"], + Params: []interface{}{code, data}, + Id: 0, + } + body, err := c.RequestResponse(request) + if err != nil { + return nil, err + } + var response struct { + Result *core.ResponseCall `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` + } + binary.ReadJSON(&response, body, &err) + if err != nil { + return nil, err + } + if response.Error != "" { + return nil, fmt.Errorf(response.Error) + } + return response.Result, nil +} + func (c *ClientJSON) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) { request := RPCRequest{ JSONRPC: "2.0", diff --git a/rpc/core/txs.go b/rpc/core/txs.go index 655c06695..cbba6b57c 100644 --- a/rpc/core/txs.go +++ b/rpc/core/txs.go @@ -51,6 +51,31 @@ func Call(address, data []byte) (*ResponseCall, error) { return &ResponseCall{Return: ret}, nil } +// Run the given code on an isolated and unpersisted state +// Cannot be used to create new contracts +func CallCode(code, data []byte) (*ResponseCall, error) { + + st := consensusState.GetState() // performs a copy + cache := mempoolReactor.Mempool.GetCache() + callee := &vm.Account{Address: Zero256} + caller := &vm.Account{Address: Zero256} + txCache := state.NewTxCache(cache) + params := vm.Params{ + BlockHeight: uint64(st.LastBlockHeight), + BlockHash: RightPadWord256(st.LastBlockHash), + BlockTime: st.LastBlockTime.Unix(), + GasLimit: 10000000, + } + + vmach := vm.NewVM(txCache, params, caller.Address) + gas := uint64(1000000000) + ret, err := vmach.Call(caller, callee, code, data, 0, &gas) + if err != nil { + return nil, err + } + return &ResponseCall{Return: ret}, nil +} + //----------------------------------------------------------------------------- func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ResponseSignTx, error) { diff --git a/rpc/handlers.go b/rpc/handlers.go index fc6a501df..7462180c4 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -1,9 +1,5 @@ package rpc -/* -TODO: support Call && GetStorage. -*/ - import ( "bytes" "encoding/json" @@ -21,7 +17,7 @@ import ( ) // cache all type information about each function up front -// (func, responseStruct, argNames) +// (func, argNames) var funcMap = map[string]*FuncWrapper{ "status": funcWrap(core.Status, []string{}), "net_info": funcWrap(core.NetInfo, []string{}), @@ -30,6 +26,7 @@ var funcMap = map[string]*FuncWrapper{ "get_account": funcWrap(core.GetAccount, []string{"address"}), "get_storage": funcWrap(core.GetStorage, []string{"address", "storage"}), "call": funcWrap(core.Call, []string{"address", "data"}), + "call_code": funcWrap(core.CallCode, []string{"code", "data"}), "list_validators": funcWrap(core.ListValidators, []string{}), "dump_storage": funcWrap(core.DumpStorage, []string{"address"}), "broadcast_tx": funcWrap(core.BroadcastTx, []string{"tx"}), @@ -39,7 +36,6 @@ var funcMap = map[string]*FuncWrapper{ } // maps camel-case function names to lower case rpc version -// populated by calls to funcWrap var reverseFuncMap = fillReverseFuncMap() // fill the map from camelcase to lowercase @@ -56,7 +52,7 @@ func fillReverseFuncMap() map[string]string { return fMap } -func initHandlers(ew *events.EventSwitch) { +func initHandlers(evsw *events.EventSwitch) { // HTTP endpoints for funcName, funcInfo := range funcMap { http.HandleFunc("/"+funcName, toHTTPHandler(funcInfo)) @@ -65,12 +61,13 @@ func initHandlers(ew *events.EventSwitch) { // JSONRPC endpoints http.HandleFunc("/", JSONRPCHandler) - w := NewWebsocketManager(ew) + w := NewWebsocketManager(evsw) // websocket endpoint http.Handle("/events", websocket.Handler(w.eventsHandler)) } //------------------------------------- +// function introspection // holds all type information for each function type FuncWrapper struct { @@ -80,6 +77,7 @@ type FuncWrapper struct { argNames []string // name of each argument } +// wraps a function for quicker introspection func funcWrap(f interface{}, args []string) *FuncWrapper { return &FuncWrapper{ f: reflect.ValueOf(f), @@ -89,6 +87,7 @@ func funcWrap(f interface{}, args []string) *FuncWrapper { } } +// return a function's argument types func funcArgTypes(f interface{}) []reflect.Type { t := reflect.TypeOf(f) n := t.NumIn() @@ -99,6 +98,7 @@ func funcArgTypes(f interface{}) []reflect.Type { return types } +// return a function's return types func funcReturnTypes(f interface{}) []reflect.Type { t := reflect.TypeOf(f) n := t.NumOut() @@ -109,6 +109,7 @@ func funcReturnTypes(f interface{}) []reflect.Type { return types } +// function introspection //----------------------------------------------------------------------------- // rpc.json diff --git a/rpc/test/client_rpc_test.go b/rpc/test/client_rpc_test.go index e13f91e03..0d455c3d3 100644 --- a/rpc/test/client_rpc_test.go +++ b/rpc/test/client_rpc_test.go @@ -31,6 +31,14 @@ func TestHTTPGetStorage(t *testing.T) { testGetStorage(t, "HTTP") } +func TestHTTPCallCode(t *testing.T) { + testCallCode(t, "HTTP") +} + +func TestHTTPCallContract(t *testing.T) { + testCall(t, "HTTP") +} + //-------------------------------------------------------------------------------- // Test the JSONRPC client @@ -57,3 +65,11 @@ func TestJSONBroadcastTx(t *testing.T) { func TestJSONGetStorage(t *testing.T) { testGetStorage(t, "JSONRPC") } + +func TestJSONCallCode(t *testing.T) { + testCallCode(t, "JSONRPC") +} + +func TestJSONCallContract(t *testing.T) { + testCall(t, "JSONRPC") +} diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 43eb7fb67..342feb10e 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "github.com/tendermint/tendermint/account" + . "github.com/tendermint/tendermint/common" "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/daemon" "github.com/tendermint/tendermint/logger" @@ -217,6 +218,30 @@ func getStorage(t *testing.T, typ string, addr, slot []byte) []byte { return resp.Value } +func callCode(t *testing.T, client rpc.Client, code, data, expected []byte) { + resp, err := client.CallCode(code, data) + if err != nil { + t.Fatal(err) + } + ret := resp.Return + // NOTE: we don't flip memory when it comes out of RETURN (?!) + if bytes.Compare(ret, LeftPadWord256(expected).Bytes()) != 0 { + t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) + } +} + +func callContract(t *testing.T, client rpc.Client, address, data, expected []byte) { + resp, err := client.Call(address, data) + if err != nil { + t.Fatal(err) + } + ret := resp.Return + // NOTE: we don't flip memory when it comes out of RETURN (?!) + if bytes.Compare(ret, LeftPadWord256(expected).Bytes()) != 0 { + t.Fatalf("Conflicting return value. Got %x, expected %x", ret, expected) + } +} + //-------------------------------------------------------------------------------- // utility verification function diff --git a/rpc/test/tests.go b/rpc/test/tests.go index 1b1ebc978..1b117b280 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -132,3 +132,68 @@ func testGetStorage(t *testing.T, typ string) { t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), expected.Bytes()) } } + +func testCallCode(t *testing.T, typ string) { + client := clients[typ] + + // add two integers and return the result + code := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3} + data := []byte{} + expected := []byte{0xb} + callCode(t, client, code, data, expected) + + // pass two ints as calldata, add, and return the result + code = []byte{0x60, 0x0, 0x35, 0x60, 0x20, 0x35, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3} + data = append(LeftPadWord256([]byte{0x5}).Bytes(), LeftPadWord256([]byte{0x6}).Bytes()...) + expected = []byte{0xb} + callCode(t, client, code, data, expected) +} + +func testCall(t *testing.T, typ string) { + client := clients[typ] + + priv := state.LoadPrivValidator(".tendermint/priv_validator.json") + _ = priv + //core.SetPrivValidator(priv) + + byteAddr, _ := hex.DecodeString(userAddr) + var byteKey [64]byte + oh, _ := hex.DecodeString(userPriv) + copy(byteKey[:], oh) + + // create the contract + amt := uint64(6969) + // this is the code we want to run when the contract is called + contractCode := []byte{0x60, 0x5, 0x60, 0x6, 0x1, 0x60, 0x0, 0x52, 0x60, 0x20, 0x60, 0x0, 0xf3} + // the is the code we need to return the contractCode when the contract is initialized + lenCode := len(contractCode) + // push code to the stack + //code := append([]byte{byte(0x60 + lenCode - 1)}, LeftPadWord256(contractCode).Bytes()...) + code := append([]byte{0x7f}, RightPadWord256(contractCode).Bytes()...) + // store it in memory + code = append(code, []byte{0x60, 0x0, 0x52}...) + // return whats in memory + //code = append(code, []byte{0x60, byte(32 - lenCode), 0x60, byte(lenCode), 0xf3}...) + code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...) + + _, receipt := broadcastTx(t, typ, byteAddr, nil, code, byteKey, amt, 1000, 1000) + if receipt.CreatesContract == 0 { + t.Fatal("This tx creates a contract") + } + if len(receipt.TxHash) == 0 { + t.Fatal("Failed to compute tx hash") + } + contractAddr := receipt.ContractAddr + if len(contractAddr) == 0 { + t.Fatal("Creates contract but resulting address is empty") + } + + // allow it to get mined + time.Sleep(time.Second * 20) + mempoolCount -= 1 + + // run a call through the contract + data := []byte{} + expected := []byte{0xb} + callContract(t, client, contractAddr, data, expected) +} From 6b5383918fd54c8c5457bc4c2ed4082049114748 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Apr 2015 02:02:03 -0500 Subject: [PATCH 7/8] rpc: fix memcount error in tests --- rpc/test/helpers.go | 4 ++-- rpc/test/tests.go | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 342feb10e..5d399cf57 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -29,7 +29,7 @@ var ( userPriv = "FDE3BD94CB327D19464027BA668194C5EFA46AE83E8419D7542CFF41F00C81972239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" userPub = "2239C21C81EA7173A6C489145490C015E05D4B97448933B708A7EC5B7B4921E3" - clients = map[string]rpc.Client{ + clients = map[string]rpc.Client{ "JSONRPC": rpc.NewClient(requestAddr, "JSONRPC"), "HTTP": rpc.NewClient(requestAddr, "HTTP"), } @@ -43,7 +43,6 @@ func decodeHex(hexStr string) []byte { return bytes } - // create a new node and sleep forever func newNode(ready chan struct{}) { // Create & start node @@ -196,6 +195,7 @@ func broadcastTx(t *testing.T, typ string, fromAddr, toAddr, data []byte, key [6 if err != nil { t.Fatal(err) } + mempoolCount += 1 return tx, resp.Receipt } diff --git a/rpc/test/tests.go b/rpc/test/tests.go index 1b117b280..8fbd81e30 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -85,11 +85,10 @@ func testBroadcastTx(t *testing.T, typ string) { } 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) + if len(txs) != mempoolCount { + t.Fatalf("The mem pool has %d txs. Expected %d", len(txs), mempoolCount) } - tx2 := txs[mempoolCount].(*types.SendTx) - mempoolCount += 1 + tx2 := txs[mempoolCount-1].(*types.SendTx) n, err := new(int64), new(error) buf1, buf2 := new(bytes.Buffer), new(bytes.Buffer) tx.WriteSignBytes(buf1, n, err) @@ -122,8 +121,10 @@ func testGetStorage(t *testing.T, typ string) { if len(contractAddr) == 0 { t.Fatal("Creates contract but resulting address is empty") } + + // allow it to get mined time.Sleep(time.Second * 20) - mempoolCount -= 1 + mempoolCount = 0 v := getStorage(t, typ, contractAddr, []byte{0x1}) got := RightPadWord256(v) @@ -190,7 +191,7 @@ func testCall(t *testing.T, typ string) { // allow it to get mined time.Sleep(time.Second * 20) - mempoolCount -= 1 + mempoolCount = 0 // run a call through the contract data := []byte{} From 7356556938be11b8d62d34aad79922957ed853f8 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 8 Apr 2015 12:30:49 -0700 Subject: [PATCH 8/8] Some renames and small fixes. --- blockchain/reactor.go | 2 +- consensus/reactor.go | 2 +- daemon/daemon.go | 6 +++--- events/events.go | 2 +- mempool/reactor.go | 2 +- p2p/pex_reactor.go | 2 +- rpc/client_methods.go | 12 ++++++------ rpc/core/accounts.go | 12 ++++++------ rpc/handlers.go | 4 ++-- rpc/test/helpers.go | 4 ++-- vm/stack.go | 6 +++--- vm/vm.go | 2 +- 12 files changed, 28 insertions(+), 28 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index f389f3c91..7a213b0f0 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -242,7 +242,7 @@ func (bcR *BlockchainReactor) BroadcastStatus() error { } // implements events.Eventable -func (bcR *BlockchainReactor) AddEventSwitch(evsw *events.EventSwitch) { +func (bcR *BlockchainReactor) SetEventSwitch(evsw *events.EventSwitch) { bcR.evsw = evsw } diff --git a/consensus/reactor.go b/consensus/reactor.go index da358e594..0e264714c 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -234,7 +234,7 @@ func (conR *ConsensusReactor) ResetToState(state *sm.State) { } // implements events.Eventable -func (conR *ConsensusReactor) AddEventSwitch(evsw *events.EventSwitch) { +func (conR *ConsensusReactor) SetEventSwitch(evsw *events.EventSwitch) { conR.evsw = evsw } diff --git a/daemon/daemon.go b/daemon/daemon.go index ceaafaa5c..0c1c2a1e0 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -82,7 +82,7 @@ func NewNode() *Node { // add the event switch to all services // they should all satisfy events.Eventable - AddEventSwitch(eventSwitch, pexReactor, bcReactor, mempoolReactor, consensusReactor) + SetEventSwitch(eventSwitch, pexReactor, bcReactor, mempoolReactor, consensusReactor) return &Node{ sw: sw, @@ -116,9 +116,9 @@ func (n *Node) Stop() { } // Add the event switch to reactors, mempool, etc. -func AddEventSwitch(evsw *events.EventSwitch, eventables ...events.Eventable) { +func SetEventSwitch(evsw *events.EventSwitch, eventables ...events.Eventable) { for _, e := range eventables { - e.AddEventSwitch(evsw) + e.SetEventSwitch(evsw) } } diff --git a/events/events.go b/events/events.go index 73d1935fe..46f01cb57 100644 --- a/events/events.go +++ b/events/events.go @@ -8,7 +8,7 @@ import ( // reactors and other modules should export // this interface to become eventable type Eventable interface { - AddEventSwitch(*EventSwitch) + SetEventSwitch(*EventSwitch) } type EventSwitch struct { diff --git a/mempool/reactor.go b/mempool/reactor.go index f6d66f67f..ae469da41 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -114,7 +114,7 @@ func (memR *MempoolReactor) BroadcastTx(tx types.Tx) error { } // implements events.Eventable -func (memR *MempoolReactor) AddEventSwitch(evsw *events.EventSwitch) { +func (memR *MempoolReactor) SetEventSwitch(evsw *events.EventSwitch) { memR.evsw = evsw } diff --git a/p2p/pex_reactor.go b/p2p/pex_reactor.go index 2c0033958..20e175fc5 100644 --- a/p2p/pex_reactor.go +++ b/p2p/pex_reactor.go @@ -211,7 +211,7 @@ func (pexR *PEXReactor) ensurePeers() { } // implements events.Eventable -func (pexR *PEXReactor) AddEventSwitch(evsw *events.EventSwitch) { +func (pexR *PEXReactor) SetEventSwitch(evsw *events.EventSwitch) { pexR.evsw = evsw } diff --git a/rpc/client_methods.go b/rpc/client_methods.go index 4f8a6b9f4..b9af5ff19 100644 --- a/rpc/client_methods.go +++ b/rpc/client_methods.go @@ -21,7 +21,7 @@ type Client interface { GenPrivAccount() (*core.ResponseGenPrivAccount, error) GetAccount(address []byte) (*core.ResponseGetAccount, error) GetBlock(height uint) (*core.ResponseGetBlock, error) - GetStorage(address []byte, storage []byte) (*core.ResponseGetStorage, error) + GetStorage(address []byte, key []byte) (*core.ResponseGetStorage, error) ListAccounts() (*core.ResponseListAccounts, error) ListValidators() (*core.ResponseListValidators, error) NetInfo() (*core.ResponseNetInfo, error) @@ -269,8 +269,8 @@ func (c *ClientHTTP) GetBlock(height uint) (*core.ResponseGetBlock, error) { return response.Result, nil } -func (c *ClientHTTP) GetStorage(address []byte, storage []byte) (*core.ResponseGetStorage, error) { - values, err := argsToURLValues([]string{"address", "storage"}, address, storage) +func (c *ClientHTTP) GetStorage(address []byte, key []byte) (*core.ResponseGetStorage, error) { + values, err := argsToURLValues([]string{"address", "key"}, address, key) if err != nil { return nil, err } @@ -390,7 +390,7 @@ func (c *ClientHTTP) NetInfo() (*core.ResponseNetInfo, error) { } func (c *ClientHTTP) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*core.ResponseSignTx, error) { - values, err := argsToURLValues([]string{"tx", "privAccounts"}, tx, privAccounts) + values, err := argsToURLValues([]string{"tx", "priv_accounts"}, tx, privAccounts) if err != nil { return nil, err } @@ -665,11 +665,11 @@ func (c *ClientJSON) GetBlock(height uint) (*core.ResponseGetBlock, error) { return response.Result, nil } -func (c *ClientJSON) GetStorage(address []byte, storage []byte) (*core.ResponseGetStorage, error) { +func (c *ClientJSON) GetStorage(address []byte, key []byte) (*core.ResponseGetStorage, error) { request := RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["GetStorage"], - Params: []interface{}{address, storage}, + Params: []interface{}{address, key}, Id: 0, } body, err := c.RequestResponse(request) diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go index 2cf2b6a14..6aee90d78 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -15,7 +15,7 @@ func GetAccount(address []byte) (*ResponseGetAccount, error) { return &ResponseGetAccount{cache.GetAccount(address)}, nil } -func GetStorage(address, storage []byte) (*ResponseGetStorage, error) { +func GetStorage(address, key []byte) (*ResponseGetStorage, error) { state := consensusState.GetState() account := state.GetAccount(address) if account == nil { @@ -24,11 +24,11 @@ func GetStorage(address, storage []byte) (*ResponseGetStorage, error) { storageRoot := account.StorageRoot storageTree := state.LoadStorage(storageRoot) - _, value := storageTree.Get(RightPadWord256(storage).Bytes()) + _, value := storageTree.Get(RightPadWord256(key).Bytes()) if value == nil { - return &ResponseGetStorage{storage, nil}, nil + return &ResponseGetStorage{key, nil}, nil } - return &ResponseGetStorage{storage, value.([]byte)}, nil + return &ResponseGetStorage{key, value.([]byte)}, nil } func ListAccounts() (*ResponseListAccounts, error) { @@ -50,9 +50,9 @@ func DumpStorage(addr []byte) (*ResponseDumpStorage, error) { return nil, fmt.Errorf("Unknown address: %X", addr) } storageRoot := account.StorageRoot - storage := state.LoadStorage(storageRoot) + storageTree := state.LoadStorage(storageRoot) storageItems := []StorageItem{} - storage.Iterate(func(key interface{}, value interface{}) bool { + storageTree.Iterate(func(key interface{}, value interface{}) bool { storageItems = append(storageItems, StorageItem{ key.([]byte), value.([]byte)}) return false diff --git a/rpc/handlers.go b/rpc/handlers.go index 7462180c4..2354a2e17 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -24,7 +24,7 @@ var funcMap = map[string]*FuncWrapper{ "blockchain": funcWrap(core.BlockchainInfo, []string{"min_height", "max_height"}), "get_block": funcWrap(core.GetBlock, []string{"height"}), "get_account": funcWrap(core.GetAccount, []string{"address"}), - "get_storage": funcWrap(core.GetStorage, []string{"address", "storage"}), + "get_storage": funcWrap(core.GetStorage, []string{"address", "key"}), "call": funcWrap(core.Call, []string{"address", "data"}), "call_code": funcWrap(core.CallCode, []string{"code", "data"}), "list_validators": funcWrap(core.ListValidators, []string{}), @@ -32,7 +32,7 @@ var funcMap = map[string]*FuncWrapper{ "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"}), + "unsafe/sign_tx": funcWrap(core.SignTx, []string{"tx", "priv_accounts"}), } // maps camel-case function names to lower case rpc version diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 5d399cf57..27505bc00 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -209,9 +209,9 @@ func dumpStorage(t *testing.T, addr []byte) core.ResponseDumpStorage { return *resp } -func getStorage(t *testing.T, typ string, addr, slot []byte) []byte { +func getStorage(t *testing.T, typ string, addr, key []byte) []byte { client := clients[typ] - resp, err := client.GetStorage(addr, slot) + resp, err := client.GetStorage(addr, key) if err != nil { t.Fatal(err) } diff --git a/vm/stack.go b/vm/stack.go index 7219051fd..41d50c702 100644 --- a/vm/stack.go +++ b/vm/stack.go @@ -32,9 +32,9 @@ func (st *Stack) useGas(gasToUse uint64) { } func (st *Stack) setErr(err error) { - //if *st.err != nil { - *st.err = err - //} + if *st.err == nil { + *st.err = err + } } func (st *Stack) Push(d Word256) { diff --git a/vm/vm.go b/vm/vm.go index e21a914f8..cbf68729d 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -659,7 +659,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case RETURN: // 0xF3 offset, size := stack.Pop64(), stack.Pop64() ret, ok := subslice(memory, offset, size, false) - if !ok || err != nil { + if !ok { return nil, firstErr(err, ErrMemoryOutOfBounds) } dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(ret), ret)