mirror of
https://github.com/fluencelabs/tendermint
synced 2025-05-20 18:21:22 +00:00
rpc: test CallCode and Call
This commit is contained in:
parent
ade13daec1
commit
5a2ff3d45b
@ -16,6 +16,7 @@ type Client interface {
|
|||||||
BlockchainInfo(minHeight uint, maxHeight uint) (*core.ResponseBlockchainInfo, error)
|
BlockchainInfo(minHeight uint, maxHeight uint) (*core.ResponseBlockchainInfo, error)
|
||||||
BroadcastTx(tx types.Tx) (*core.ResponseBroadcastTx, error)
|
BroadcastTx(tx types.Tx) (*core.ResponseBroadcastTx, error)
|
||||||
Call(address []byte, data []byte) (*core.ResponseCall, error)
|
Call(address []byte, data []byte) (*core.ResponseCall, error)
|
||||||
|
CallCode(code []byte, data []byte) (*core.ResponseCall, error)
|
||||||
DumpStorage(addr []byte) (*core.ResponseDumpStorage, error)
|
DumpStorage(addr []byte) (*core.ResponseDumpStorage, error)
|
||||||
GenPrivAccount() (*core.ResponseGenPrivAccount, error)
|
GenPrivAccount() (*core.ResponseGenPrivAccount, error)
|
||||||
GetAccount(address []byte) (*core.ResponseGetAccount, 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
|
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) {
|
func (c *ClientHTTP) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) {
|
||||||
values, err := argsToURLValues([]string{"addr"}, addr)
|
values, err := argsToURLValues([]string{"addr"}, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -499,6 +530,33 @@ func (c *ClientJSON) Call(address []byte, data []byte) (*core.ResponseCall, erro
|
|||||||
return response.Result, nil
|
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) {
|
func (c *ClientJSON) DumpStorage(addr []byte) (*core.ResponseDumpStorage, error) {
|
||||||
request := RPCRequest{
|
request := RPCRequest{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
|
@ -51,6 +51,31 @@ func Call(address, data []byte) (*ResponseCall, error) {
|
|||||||
return &ResponseCall{Return: ret}, nil
|
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) {
|
func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ResponseSignTx, error) {
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
/*
|
|
||||||
TODO: support Call && GetStorage.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -21,7 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// cache all type information about each function up front
|
// cache all type information about each function up front
|
||||||
// (func, responseStruct, argNames)
|
// (func, argNames)
|
||||||
var funcMap = map[string]*FuncWrapper{
|
var funcMap = map[string]*FuncWrapper{
|
||||||
"status": funcWrap(core.Status, []string{}),
|
"status": funcWrap(core.Status, []string{}),
|
||||||
"net_info": funcWrap(core.NetInfo, []string{}),
|
"net_info": funcWrap(core.NetInfo, []string{}),
|
||||||
@ -30,6 +26,7 @@ var funcMap = map[string]*FuncWrapper{
|
|||||||
"get_account": funcWrap(core.GetAccount, []string{"address"}),
|
"get_account": funcWrap(core.GetAccount, []string{"address"}),
|
||||||
"get_storage": funcWrap(core.GetStorage, []string{"address", "storage"}),
|
"get_storage": funcWrap(core.GetStorage, []string{"address", "storage"}),
|
||||||
"call": funcWrap(core.Call, []string{"address", "data"}),
|
"call": funcWrap(core.Call, []string{"address", "data"}),
|
||||||
|
"call_code": funcWrap(core.CallCode, []string{"code", "data"}),
|
||||||
"list_validators": funcWrap(core.ListValidators, []string{}),
|
"list_validators": funcWrap(core.ListValidators, []string{}),
|
||||||
"dump_storage": funcWrap(core.DumpStorage, []string{"address"}),
|
"dump_storage": funcWrap(core.DumpStorage, []string{"address"}),
|
||||||
"broadcast_tx": funcWrap(core.BroadcastTx, []string{"tx"}),
|
"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
|
// maps camel-case function names to lower case rpc version
|
||||||
// populated by calls to funcWrap
|
|
||||||
var reverseFuncMap = fillReverseFuncMap()
|
var reverseFuncMap = fillReverseFuncMap()
|
||||||
|
|
||||||
// fill the map from camelcase to lowercase
|
// fill the map from camelcase to lowercase
|
||||||
@ -56,7 +52,7 @@ func fillReverseFuncMap() map[string]string {
|
|||||||
return fMap
|
return fMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func initHandlers(ew *events.EventSwitch) {
|
func initHandlers(evsw *events.EventSwitch) {
|
||||||
// HTTP endpoints
|
// HTTP endpoints
|
||||||
for funcName, funcInfo := range funcMap {
|
for funcName, funcInfo := range funcMap {
|
||||||
http.HandleFunc("/"+funcName, toHTTPHandler(funcInfo))
|
http.HandleFunc("/"+funcName, toHTTPHandler(funcInfo))
|
||||||
@ -65,12 +61,13 @@ func initHandlers(ew *events.EventSwitch) {
|
|||||||
// JSONRPC endpoints
|
// JSONRPC endpoints
|
||||||
http.HandleFunc("/", JSONRPCHandler)
|
http.HandleFunc("/", JSONRPCHandler)
|
||||||
|
|
||||||
w := NewWebsocketManager(ew)
|
w := NewWebsocketManager(evsw)
|
||||||
// websocket endpoint
|
// websocket endpoint
|
||||||
http.Handle("/events", websocket.Handler(w.eventsHandler))
|
http.Handle("/events", websocket.Handler(w.eventsHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
// function introspection
|
||||||
|
|
||||||
// holds all type information for each function
|
// holds all type information for each function
|
||||||
type FuncWrapper struct {
|
type FuncWrapper struct {
|
||||||
@ -80,6 +77,7 @@ type FuncWrapper struct {
|
|||||||
argNames []string // name of each argument
|
argNames []string // name of each argument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wraps a function for quicker introspection
|
||||||
func funcWrap(f interface{}, args []string) *FuncWrapper {
|
func funcWrap(f interface{}, args []string) *FuncWrapper {
|
||||||
return &FuncWrapper{
|
return &FuncWrapper{
|
||||||
f: reflect.ValueOf(f),
|
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 {
|
func funcArgTypes(f interface{}) []reflect.Type {
|
||||||
t := reflect.TypeOf(f)
|
t := reflect.TypeOf(f)
|
||||||
n := t.NumIn()
|
n := t.NumIn()
|
||||||
@ -99,6 +98,7 @@ func funcArgTypes(f interface{}) []reflect.Type {
|
|||||||
return types
|
return types
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// return a function's return types
|
||||||
func funcReturnTypes(f interface{}) []reflect.Type {
|
func funcReturnTypes(f interface{}) []reflect.Type {
|
||||||
t := reflect.TypeOf(f)
|
t := reflect.TypeOf(f)
|
||||||
n := t.NumOut()
|
n := t.NumOut()
|
||||||
@ -109,6 +109,7 @@ func funcReturnTypes(f interface{}) []reflect.Type {
|
|||||||
return types
|
return types
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function introspection
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// rpc.json
|
// rpc.json
|
||||||
|
|
||||||
|
@ -31,6 +31,14 @@ func TestHTTPGetStorage(t *testing.T) {
|
|||||||
testGetStorage(t, "HTTP")
|
testGetStorage(t, "HTTP")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHTTPCallCode(t *testing.T) {
|
||||||
|
testCallCode(t, "HTTP")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPCallContract(t *testing.T) {
|
||||||
|
testCall(t, "HTTP")
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------
|
||||||
// Test the JSONRPC client
|
// Test the JSONRPC client
|
||||||
|
|
||||||
@ -57,3 +65,11 @@ func TestJSONBroadcastTx(t *testing.T) {
|
|||||||
func TestJSONGetStorage(t *testing.T) {
|
func TestJSONGetStorage(t *testing.T) {
|
||||||
testGetStorage(t, "JSONRPC")
|
testGetStorage(t, "JSONRPC")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestJSONCallCode(t *testing.T) {
|
||||||
|
testCallCode(t, "JSONRPC")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONCallContract(t *testing.T) {
|
||||||
|
testCall(t, "JSONRPC")
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/tendermint/tendermint/account"
|
"github.com/tendermint/tendermint/account"
|
||||||
|
. "github.com/tendermint/tendermint/common"
|
||||||
"github.com/tendermint/tendermint/config"
|
"github.com/tendermint/tendermint/config"
|
||||||
"github.com/tendermint/tendermint/daemon"
|
"github.com/tendermint/tendermint/daemon"
|
||||||
"github.com/tendermint/tendermint/logger"
|
"github.com/tendermint/tendermint/logger"
|
||||||
@ -217,6 +218,30 @@ func getStorage(t *testing.T, typ string, addr, slot []byte) []byte {
|
|||||||
return resp.Value
|
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
|
// utility verification function
|
||||||
|
|
||||||
|
@ -132,3 +132,68 @@ func testGetStorage(t *testing.T, typ string) {
|
|||||||
t.Fatalf("Wrong storage value. Got %x, expected %x", got.Bytes(), expected.Bytes())
|
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)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user