diff --git a/rpc/core/names.go b/rpc/core/names.go new file mode 100644 index 00000000..928afb15 --- /dev/null +++ b/rpc/core/names.go @@ -0,0 +1,17 @@ +package core + +import ( + "fmt" + + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// XXX: need we be careful about rendering bytes as string or is that their problem ? +func NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) { + st := consensusState.GetState() // performs a copy + entry := st.GetNameRegEntry(name) + if entry == nil { + return nil, fmt.Errorf("Name %s not found", name) + } + return &ctypes.ResponseNameRegEntry{entry}, nil +} diff --git a/rpc/core/routes.go b/rpc/core/routes.go index f65d58e2..198c76f6 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -20,6 +20,7 @@ var Routes = map[string]*rpc.RPCFunc{ "broadcast_tx": rpc.NewRPCFunc(BroadcastTx, []string{"tx"}), "list_unconfirmed_txs": rpc.NewRPCFunc(ListUnconfirmedTxs, []string{}), "list_accounts": rpc.NewRPCFunc(ListAccounts, []string{}), + "name_reg_entry": rpc.NewRPCFunc(NameRegEntry, []string{"name"}), "unsafe/gen_priv_account": rpc.NewRPCFunc(GenPrivAccount, []string{}), "unsafe/sign_tx": rpc.NewRPCFunc(SignTx, []string{"tx", "privAccounts"}), } diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 9239695f..f9ac1a10 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -100,3 +100,7 @@ type ResponseDumpConsensusState struct { RoundState string `json:"round_state"` PeerRoundStates []string `json:"peer_round_states"` } + +type ResponseNameRegEntry struct { + Entry *types.NameRegEntry `json:"entry"` +} diff --git a/rpc/core_client/client.go b/rpc/core_client/client.go index 3254aad2..47ec5c1e 100644 --- a/rpc/core_client/client.go +++ b/rpc/core_client/client.go @@ -5,8 +5,6 @@ import ( "fmt" "github.com/tendermint/tendermint/binary" rpctypes "github.com/tendermint/tendermint/rpc/types" - // NOTE: do not import rpc/core. - // What kind of client imports all of core logic? :P "io/ioutil" "net/http" "net/url" @@ -30,6 +28,7 @@ var reverseFuncMap = map[string]string{ "DumpStorage": "dump_storage", "BroadcastTx": "broadcast_tx", "ListAccounts": "list_accounts", + "NameRegEntry": "name_reg_entry", "GenPrivAccount": "unsafe/gen_priv_account", "SignTx": "unsafe/sign_tx", } diff --git a/rpc/core_client/client_methods.go b/rpc/core_client/client_methods.go index aa48dcff..cf3804dc 100644 --- a/rpc/core_client/client_methods.go +++ b/rpc/core_client/client_methods.go @@ -28,6 +28,7 @@ type Client interface { ListAccounts() (*ctypes.ResponseListAccounts, error) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) ListValidators() (*ctypes.ResponseListValidators, error) + NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) NetInfo() (*ctypes.ResponseNetInfo, error) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseSignTx, error) Status() (*ctypes.ResponseStatus, error) @@ -453,6 +454,36 @@ func (c *ClientHTTP) ListValidators() (*ctypes.ResponseListValidators, error) { return response.Result, nil } +func (c *ClientHTTP) NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) { + values, err := argsToURLValues([]string{"name"}, name) + if err != nil { + return nil, err + } + resp, err := http.PostForm(c.addr+reverseFuncMap["NameRegEntry"], 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 *ctypes.ResponseNameRegEntry `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) NetInfo() (*ctypes.ResponseNetInfo, error) { values, err := argsToURLValues(nil) if err != nil { @@ -921,6 +952,33 @@ func (c *ClientJSON) ListValidators() (*ctypes.ResponseListValidators, error) { return response.Result, nil } +func (c *ClientJSON) NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) { + request := rpctypes.RPCRequest{ + JSONRPC: "2.0", + Method: reverseFuncMap["NameRegEntry"], + Params: []interface{}{name}, + Id: 0, + } + body, err := c.RequestResponse(request) + if err != nil { + return nil, err + } + var response struct { + Result *ctypes.ResponseNameRegEntry `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) NetInfo() (*ctypes.ResponseNetInfo, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", diff --git a/rpc/test/client_rpc_test.go b/rpc/test/client_rpc_test.go index 0ced2e66..2d9af88a 100644 --- a/rpc/test/client_rpc_test.go +++ b/rpc/test/client_rpc_test.go @@ -40,6 +40,10 @@ func TestHTTPCallContract(t *testing.T) { testCall(t, "HTTP") } +func TestHTTPNameReg(t *testing.T) { + testNameReg(t, "HTTP") +} + //-------------------------------------------------------------------------------- // Test the JSONRPC client @@ -74,3 +78,7 @@ func TestJSONCallCode(t *testing.T) { func TestJSONCallContract(t *testing.T) { testCall(t, "JSONRPC") } + +func TestJSONNameReg(t *testing.T) { + testNameReg(t, "JSONRPC") +} diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index da05ccfd..bea0253d 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -120,6 +120,13 @@ func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, return tx } +func makeDefaultNameTx(t *testing.T, typ string, name, value []byte, amt, fee uint64) *types.NameTx { + nonce := getNonce(t, typ, user[0].Address) + tx := types.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce) + tx.Sign(user[0]) + return tx +} + //------------------------------------------------------------------------------- // rpc call wrappers (fail on err) @@ -210,6 +217,16 @@ func callContract(t *testing.T, client cclient.Client, address, data, expected [ } } +// get the namereg entry +func getNameRegEntry(t *testing.T, typ string, name []byte) *types.NameRegEntry { + client := clients[typ] + r, err := client.NameRegEntry(name) + if err != nil { + t.Fatal(err) + } + return r.Entry +} + //-------------------------------------------------------------------------------- // utility verification function diff --git a/rpc/test/tests.go b/rpc/test/tests.go index ca9221ea..8f6359d4 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -190,3 +190,33 @@ func testCall(t *testing.T, typ string) { expected := []byte{0xb} callContract(t, client, contractAddr, data, expected) } + +func testNameReg(t *testing.T, typ string) { + con := newWSCon(t) + eid := types.EventStringNewBlock() + subscribe(t, con, eid) + defer func() { + unsubscribe(t, con, eid) + con.Close() + }() + + amt, fee := uint64(6969), uint64(1000) + // since entries ought to be unique and these run against different clients, we append the typ + name := []byte("ye-old-domain-name-" + typ) + data := []byte("these are amongst the things I wish to bestow upon the youth of generations come: a safe supply of honey, and a better money. For what else shall they need?") + tx := makeDefaultNameTx(t, typ, name, data, amt, fee) + broadcastTx(t, typ, tx) + + // allow it to get mined + waitForEvent(t, con, eid, true, func() { + }, func(eid string, b []byte) error { + return nil + }) + + entry := getNameRegEntry(t, typ, name) + + if bytes.Compare(entry.Data, data) != 0 { + t.Fatal(fmt.Sprintf("Got %s, expected %s", entry.Data, data)) + } + +} diff --git a/types/tx_utils.go b/types/tx_utils.go index eb5491ad..beae2e86 100644 --- a/types/tx_utils.go +++ b/types/tx_utils.go @@ -103,6 +103,43 @@ func (tx *CallTx) Sign(chainID string, privAccount *account.PrivAccount) { tx.Input.Signature = privAccount.Sign(chainID, tx) } +//---------------------------------------------------------------------------- +// NameTx interface for creating tx + +func NewNameTx(st AccountGetter, from account.PubKey, name, data []byte, amt, fee uint64) (*NameTx, error) { + addr := from.Address() + acc := st.GetAccount(addr) + if acc == nil { + return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from) + } + + nonce := uint64(acc.Sequence) + return NewNameTxWithNonce(from, name, data, amt, fee, nonce), nil +} + +func NewNameTxWithNonce(from account.PubKey, name, data []byte, amt, fee, nonce uint64) *NameTx { + addr := from.Address() + input := &TxInput{ + Address: addr, + Amount: amt, + Sequence: uint(nonce) + 1, + Signature: account.SignatureEd25519{}, + PubKey: from, + } + + return &NameTx{ + Input: input, + Name: name, + Data: data, + Fee: fee, + } +} + +func (tx *NameTx) Sign(privAccount *account.PrivAccount) { + tx.Input.PubKey = privAccount.PubKey + tx.Input.Signature = privAccount.Sign(tx) +} + //---------------------------------------------------------------------------- // BondTx interface for adding inputs/outputs and adding signatures