From 5a2ff3d45b8ffec673b59dae9498f000ab052f86 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 2 Apr 2015 19:15:23 -0700 Subject: [PATCH] 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) +}