From dd1181f0d4835603f554fe5869a221d54aac41d2 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 31 Mar 2015 04:53:34 -0700 Subject: [PATCH] rpc: GetStorage and Call methods. Tests. --- rpc/core/accounts.go | 25 +++++++++++ rpc/core/mempool.go | 2 +- rpc/core/pipe.go | 5 +++ rpc/core/responses.go | 11 +++++ rpc/core/txs.go | 45 +++++++++++++++++++ rpc/handlers.go | 2 + rpc/test/http_rpc_test.go | 71 +++++++++++++++++------------ rpc/test/json_rpc_test.go | 16 +++---- rpc/test/test.go | 94 ++++++++++++++++++++++++++++++++++++--- 9 files changed, 229 insertions(+), 42 deletions(-) diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go index abf208897..3c6262540 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -3,6 +3,7 @@ package core import ( "fmt" "github.com/tendermint/tendermint2/account" + . "github.com/tendermint/tendermint2/common" ) func GenPrivAccount() (*ResponseGenPrivAccount, error) { @@ -14,6 +15,22 @@ func GetAccount(addr []byte) (*ResponseGetAccount, error) { return &ResponseGetAccount{cache.GetAccount(addr)}, nil } +func GetStorage(address, slot []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) + + _, value := storage.Get(RightPadWord256(slot).Bytes()) + if value == nil { + return &ResponseGetStorage{slot, nil}, nil + } + return &ResponseGetStorage{slot, value.([]byte)}, nil +} + func ListAccounts() (*ResponseListAccounts, error) { var blockHeight uint var accounts []*account.Account @@ -26,6 +43,14 @@ func ListAccounts() (*ResponseListAccounts, error) { return &ResponseListAccounts{blockHeight, accounts}, nil } +func GetStorage(address, storage []byte) (*ResponseGetStorage, error) { + cache := mempoolReactor.Mempool.GetCache() + addr, slot := RightPadWord256(address), RightPadWord256(storage) + value := cache.GetStorage(addr, slot) + fmt.Printf("STORAGE: %x, %x, %x\n", addr, slot, value) + return &ResponseGetStorage{storage, value.Bytes()}, nil +} + func DumpStorage(addr []byte) (*ResponseDumpStorage, error) { state := consensusState.GetState() account := state.GetAccount(addr) diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index 3f51a2177..c55876d31 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -28,7 +28,7 @@ func BroadcastTx(tx types.Tx) (*ResponseBroadcastTx, error) { var contractAddr []byte // check if creates new contract if callTx, ok := tx.(*types.CallTx); ok { - if callTx.Address == nil { + if len(callTx.Address) == 0 { createsContract = 1 contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence)) } diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 4776a4e39..9562bd533 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -5,6 +5,7 @@ import ( "github.com/tendermint/tendermint2/consensus" mempl "github.com/tendermint/tendermint2/mempool" "github.com/tendermint/tendermint2/p2p" + "github.com/tendermint/tendermint2/state" ) var blockStore *bc.BlockStore @@ -27,3 +28,7 @@ func SetMempoolReactor(mr *mempl.MempoolReactor) { func SetSwitch(sw *p2p.Switch) { p2pSwitch = sw } + +func SetPrivValidator(priv *state.PrivValidator) { + consensusState.SetPrivValidator(priv) +} diff --git a/rpc/core/responses.go b/rpc/core/responses.go index a3ef7739b..61f06d9c5 100644 --- a/rpc/core/responses.go +++ b/rpc/core/responses.go @@ -14,6 +14,17 @@ type ResponseGetAccount struct { Account *account.Account } +type ResponseGetStorage struct { + Key []byte + Value []byte +} + +type ResponseCall struct { + Return []byte + GasUsed uint64 + // TODO ... +} + type ResponseListAccounts struct { BlockHeight uint Accounts []*account.Account diff --git a/rpc/core/txs.go b/rpc/core/txs.go index d33079e7b..37f062f25 100644 --- a/rpc/core/txs.go +++ b/rpc/core/txs.go @@ -3,9 +3,54 @@ package core import ( "fmt" "github.com/tendermint/tendermint2/account" + . "github.com/tendermint/tendermint2/common" + "github.com/tendermint/tendermint2/state" "github.com/tendermint/tendermint2/types" + "github.com/tendermint/tendermint2/vm" ) +func toVMAccount(acc *account.Account) *vm.Account { + return &vm.Account{ + Address: RightPadWord256(acc.Address), + Balance: acc.Balance, + Code: acc.Code, // This is crazy. + Nonce: uint64(acc.Sequence), + StorageRoot: RightPadWord256(acc.StorageRoot), + Other: acc.PubKey, + } +} + +//----------------------------------------------------------------------------- + +// Run a contract's code on an isolated and unpersisted state +// Cannot be used to create new contracts +func Call(address, data []byte) (*ResponseCall, error) { + + st := consensusState.GetState() // performs a copy + cache := mempoolReactor.Mempool.GetCache() + outAcc := cache.GetAccount(address) + if outAcc == nil { + return nil, fmt.Errorf("Account %x does not exist", address) + } + callee := toVMAccount(outAcc) + 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, 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 858786024..703be74c4 100644 --- a/rpc/handlers.go +++ b/rpc/handlers.go @@ -22,6 +22,8 @@ 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"}), + "call": funcWrap(core.Call, []string{"address", "data"}), "list_validators": funcWrap(core.ListValidators, []string{}), "dump_storage": funcWrap(core.DumpStorage, []string{"address"}), "broadcast_tx": funcWrap(core.BroadcastTx, []string{"tx"}), diff --git a/rpc/test/http_rpc_test.go b/rpc/test/http_rpc_test.go index 4de03b783..97e886809 100644 --- a/rpc/test/http_rpc_test.go +++ b/rpc/test/http_rpc_test.go @@ -6,14 +6,16 @@ import ( "encoding/json" "fmt" "github.com/tendermint/tendermint2/binary" + . "github.com/tendermint/tendermint2/common" "github.com/tendermint/tendermint2/config" "github.com/tendermint/tendermint2/merkle" "github.com/tendermint/tendermint2/rpc/core" + "github.com/tendermint/tendermint2/state" "github.com/tendermint/tendermint2/types" "io/ioutil" "net/http" - "net/url" "testing" + "time" ) func TestHTTPStatus(t *testing.T) { @@ -88,16 +90,16 @@ 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, byteKey, amt) - checkTx(t, byteAddr, priv, tx) + tx, priv := signTx(t, "HTTP", 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, byteKey, amt) - checkTx(t, byteAddr, priv, tx) + tx, priv = signTx(t, "HTTP", 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, byteKey, amt) - checkTx(t, byteAddr, priv, tx) + tx, priv = signTx(t, "HTTP", byteAddr, toAddr, nil, byteKey, amt, 0, 0) + checkTx(t, byteAddr, priv, tx.(*types.SendTx)) } func TestHTTPBroadcastTx(t *testing.T) { @@ -108,27 +110,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, priv := signTx(t, "HTTP", byteAddr, toAddr, byteKey, amt) - checkTx(t, byteAddr, priv, tx) - - n, w := new(int64), new(bytes.Buffer) - var err error - binary.WriteJSON(tx, w, n, &err) - if err != nil { - t.Fatal(err) - } - b := w.Bytes() - - var status struct { - Status string - Data core.ResponseBroadcastTx - Error string - } - requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status) - if status.Status == "ERROR" { - t.Fatal(status.Error) - } - receipt := status.Data.Receipt + tx, receipt := broadcastTx(t, "HTTP", byteAddr, toAddr, nil, byteKey, amt, 0, 0) if receipt.CreatesContract > 0 { t.Fatal("This tx does not create a contract") } @@ -148,6 +130,39 @@ func TestHTTPBroadcastTx(t *testing.T) { } +func TestHTTPGetStorage(t *testing.T) { + 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) + + amt := uint64(1100) + code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} + _, receipt := broadcastTx(t, "HTTP", 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") + } + time.Sleep(time.Second * 20) + + v := getStorage(t, 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/json_rpc_test.go b/rpc/test/json_rpc_test.go index 5d44429a5..7da00ccc3 100644 --- a/rpc/test/json_rpc_test.go +++ b/rpc/test/json_rpc_test.go @@ -107,16 +107,16 @@ func TestJSONSignedTx(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, "JSONRPC", byteAddr, toAddr, byteKey, amt) - checkTx(t, byteAddr, priv, tx) + 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, byteKey, amt) - checkTx(t, byteAddr, priv, tx) + 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, byteKey, amt) - checkTx(t, byteAddr, priv, tx) + tx, priv = signTx(t, "JSONRPC", byteAddr, toAddr, nil, byteKey, amt, 0, 0) + checkTx(t, byteAddr, priv, tx.(*types.SendTx)) } func TestJSONBroadcastTx(t *testing.T) { @@ -127,8 +127,8 @@ func TestJSONBroadcastTx(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, "JSONRPC", byteAddr, toAddr, byteKey, amt) - checkTx(t, byteAddr, priv, tx) + 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 diff --git a/rpc/test/test.go b/rpc/test/test.go index e391fee77..0923508bc 100644 --- a/rpc/test/test.go +++ b/rpc/test/test.go @@ -108,7 +108,7 @@ func getAccount(t *testing.T, typ string, addr []byte) *account.Account { return status.Data.Account } -func makeTx(t *testing.T, typ string, from, to []byte, amt uint64) *types.SendTx { +func makeSendTx(t *testing.T, typ string, from, to []byte, amt uint64) *types.SendTx { acc := getAccount(t, typ, from) nonce := 0 if acc != nil { @@ -138,6 +138,33 @@ func makeTx(t *testing.T, typ string, from, to []byte, amt uint64) *types.SendTx return tx } +func makeCallTx(t *testing.T, typ string, from, to, data []byte, amt, gaslim, fee uint64) *types.CallTx { + acc := getAccount(t, typ, from) + nonce := 0 + if acc != nil { + nonce = int(acc.Sequence) + 1 + } + + bytePub, err := hex.DecodeString(userPub) + if err != nil { + t.Fatal(err) + } + tx := &types.CallTx{ + Input: &types.TxInput{ + Address: from, + Amount: amt, + Sequence: uint(nonce), + Signature: account.SignatureEd25519{}, + PubKey: account.PubKeyEd25519(bytePub), + }, + Address: to, + GasLimit: gaslim, + Fee: fee, + Data: data, + } + return tx +} + func requestResponse(t *testing.T, method string, values url.Values, status interface{}) { resp, err := http.PostForm(requestAddr+method, values) if err != nil { @@ -154,8 +181,13 @@ func requestResponse(t *testing.T, method string, values url.Values, status inte } } -func signTx(t *testing.T, typ string, fromAddr, toAddr []byte, key [64]byte, amt uint64) (*types.SendTx, *account.PrivAccount) { - tx := makeTx(t, typ, fromAddr, toAddr, amt) +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 { + tx = makeSendTx(t, typ, fromAddr, toAddr, amt) + } else { + tx = makeCallTx(t, typ, fromAddr, toAddr, data, amt, gaslim, fee) + } n, w := new(int64), new(bytes.Buffer) var err error @@ -185,8 +217,60 @@ func signTx(t *testing.T, typ string, fromAddr, toAddr []byte, key [64]byte, amt t.Fatal(status.Error) } response := status.Data - tx = response.Tx.(*types.SendTx) - return tx, privAcc + //tx = response.Tx.(*types.SendTx) + return response.Tx, privAcc +} + +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) + if err != nil { + t.Fatal(err) + } + b := w.Bytes() + + var status struct { + Status string + Data core.ResponseBroadcastTx + Error string + } + requestResponse(t, "broadcast_tx", url.Values{"tx": {string(b)}}, &status) + if status.Status == "ERROR" { + t.Fatal(status.Error) + } + return tx, status.Data.Receipt +} + +func dumpStorage(t *testing.T, addr []byte) core.ResponseDumpStorage { + addrString := "\"" + hex.EncodeToString(addr) + "\"" + var status struct { + Status string + Data core.ResponseDumpStorage + Error string + } + requestResponse(t, "dump_storage", url.Values{"address": {addrString}}, &status) + if status.Status != "OK" { + t.Fatal(status.Error) + } + return status.Data +} + +func getStorage(t *testing.T, addr, slot []byte) []byte { + addrString := "\"" + hex.EncodeToString(addr) + "\"" + slotString := "\"" + hex.EncodeToString(slot) + "\"" + var status struct { + Status string + Data core.ResponseGetStorage + Error string + } + requestResponse(t, "get_storage", url.Values{"address": {addrString}, "storage": {slotString}}, &status) + if status.Status != "OK" { + t.Fatal(status.Error) + } + return status.Data.Value } func checkTx(t *testing.T, fromAddr []byte, priv *account.PrivAccount, tx *types.SendTx) {