From 8631d5085ea6d5fed299116bfef95164c7c01844 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 22 May 2015 16:53:10 -0400 Subject: [PATCH 01/13] name reg --- state/block_cache.go | 78 +++++++++++++++++++++++++++++++++++ state/execution.go | 98 +++++++++++++++++++++++++++++++++++++++++++- state/genesis.go | 6 +++ state/state.go | 49 ++++++++++++++++++++++ state/state_test.go | 85 ++++++++++++++++++++++++++++++++++++++ types/names.go | 8 ++++ types/tx.go | 25 +++++++++++ 7 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 types/names.go diff --git a/state/block_cache.go b/state/block_cache.go index 2eda83fa9..102d26253 100644 --- a/state/block_cache.go +++ b/state/block_cache.go @@ -9,6 +9,7 @@ import ( . "github.com/tendermint/tendermint/common" dbm "github.com/tendermint/tendermint/db" "github.com/tendermint/tendermint/merkle" + "github.com/tendermint/tendermint/types" ) func makeStorage(db dbm.DB, root []byte) merkle.Tree { @@ -27,6 +28,7 @@ type BlockCache struct { backend *State accounts map[string]accountInfo storages map[Tuple256]storageInfo + names map[string]nameInfo } func NewBlockCache(backend *State) *BlockCache { @@ -35,6 +37,7 @@ func NewBlockCache(backend *State) *BlockCache { backend: backend, accounts: make(map[string]accountInfo), storages: make(map[Tuple256]storageInfo), + names: make(map[string]nameInfo), } } @@ -123,6 +126,44 @@ func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) { // BlockCache.storage //------------------------------------- +// BlockCache.names + +func (cache *BlockCache) GetNameRegEntry(name []byte) *types.NameRegEntry { + entry, removed, _ := cache.names[string(name)].unpack() + if removed { + return nil + } else if entry != nil { + return entry + } else { + entry = cache.backend.GetNameRegEntry(name) + cache.names[string(name)] = nameInfo{entry, false, false} + return entry + } +} + +func (cache *BlockCache) UpdateNameRegEntry(entry *types.NameRegEntry) { + name := entry.Name + // SANITY CHECK + _, removed, _ := cache.names[string(name)].unpack() + if removed { + panic("UpdateNameRegEntry on a removed name") + } + // SANITY CHECK END + cache.names[string(name)] = nameInfo{entry, false, true} +} + +func (cache *BlockCache) RemoveNameRegEntry(name []byte) { + // SANITY CHECK + _, removed, _ := cache.names[string(name)].unpack() + if removed { + panic("RemoveNameRegEntry on a removed entry") + } + // SANITY CHECK END + cache.names[string(name)] = nameInfo{nil, true, false} +} + +// BlockCache.names +//------------------------------------- // CONTRACT the updates are in deterministic order. func (cache *BlockCache) Sync() { @@ -202,6 +243,33 @@ func (cache *BlockCache) Sync() { } } + // Determine order for names + // note names may be of any length less than some limit + // and are arbitrary byte arrays... + nameStrs := []string{} + for nameStr := range cache.names { + nameStrs = append(nameStrs, nameStr) + } + sort.Strings(nameStrs) + + // Update or delete names. + for _, nameStr := range nameStrs { + entry, removed, dirty := cache.names[nameStr].unpack() + if removed { + removed := cache.backend.RemoveNameRegEntry(entry.Name) + if !removed { + panic(Fmt("Could not remove namereg entry to be removed: %X", entry.Name)) + } + } else { + if entry == nil { + continue + } + if dirty { + cache.backend.UpdateNameRegEntry(entry) + } + } + } + } //----------------------------------------------------------------------------- @@ -225,3 +293,13 @@ type storageInfo struct { func (stjInfo storageInfo) unpack() (Word256, bool) { return stjInfo.value, stjInfo.dirty } + +type nameInfo struct { + name *types.NameRegEntry + removed bool + dirty bool +} + +func (nInfo nameInfo) unpack() (*types.NameRegEntry, bool, bool) { + return nInfo.name, nInfo.removed, nInfo.dirty +} diff --git a/state/execution.go b/state/execution.go index e46a2150b..a9f3ca823 100644 --- a/state/execution.go +++ b/state/execution.go @@ -296,7 +296,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // TODO: do something with fees fees := uint64(0) - _s := blockCache.State() // hack to access validators and event switch. + _s := blockCache.State() // hack to access validators and block height // Exec tx switch tx := tx_.(type) { @@ -478,6 +478,102 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea return nil + case *types.NameTx: + var inAcc *account.Account + + // Validate input + inAcc = blockCache.GetAccount(tx.Input.Address) + if inAcc == nil { + log.Debug(Fmt("Can't find in account %X", tx.Input.Address)) + return types.ErrTxInvalidAddress + } + // pubKey should be present in either "inAcc" or "tx.Input" + if err := checkInputPubKey(inAcc, tx.Input); err != nil { + log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) + return err + } + signBytes := account.SignBytes(tx) + err := validateInput(inAcc, signBytes, tx.Input) + if err != nil { + log.Debug(Fmt("validateInput failed on %X:", tx.Input.Address)) + return err + } + // fee is in addition to the amount which is used to determine the TTL + if tx.Input.Amount < tx.Fee { + log.Debug(Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) + return types.ErrTxInsufficientFunds + } + + value := tx.Input.Amount - tx.Fee + + // let's say cost of a name for one block is len(data) + 32 + // TODO: the casting is dangerous and things can overflow (below)! + // we should make LastBlockHeight a uint64 + costPerBlock := uint(len(tx.Data) + 32) + expiresIn := uint(value / uint64(costPerBlock)) + + // check if the name exists + entry := blockCache.GetNameRegEntry(tx.Name) + + if entry != nil { + var expired bool + // if the entry already exists, and hasn't expired, we must be owner + if entry.Expires > _s.LastBlockHeight { + // ensure we are owner + if bytes.Compare(entry.Owner, tx.Input.Address) != 0 { + log.Debug(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name)) + return types.ErrTxInvalidAddress // (?) + } + } else { + expired = true + } + + // no value and empty data means delete the entry + if value == 0 && len(tx.Data) == 0 { + // maybe we reward you for telling us we can delete this crap + // (owners if not expired, anyone if expired) + blockCache.RemoveNameRegEntry(entry.Name) + } else { + // update the entry by bumping the expiry + // and changing the data + if expired { + entry.Expires = _s.LastBlockHeight + expiresIn + } else { + // since the size of the data may have changed + // we use the total amount of "credit" + credit := uint64((entry.Expires - _s.LastBlockHeight) * uint(len(entry.Data))) + credit += value + expiresIn = uint(credit) / costPerBlock + entry.Expires = _s.LastBlockHeight + expiresIn + } + entry.Data = tx.Data + blockCache.UpdateNameRegEntry(entry) + } + } else { + if expiresIn < 5 { + return fmt.Errorf("Names must be registered for at least 5 blocks") + } + // entry does not exist, so create it + entry = &types.NameRegEntry{ + Name: tx.Name, + Owner: tx.Input.Address, + Data: tx.Data, + Expires: _s.LastBlockHeight + expiresIn, + } + blockCache.UpdateNameRegEntry(entry) + } + + // TODO: something with the value sent? + + // Good! + inAcc.Sequence += 1 + inAcc.Balance -= value + blockCache.UpdateAccount(inAcc) + + // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? + + return nil + case *types.BondTx: valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address()) if valInfo != nil { diff --git a/state/genesis.go b/state/genesis.go index 691597ce0..2024e2117 100644 --- a/state/genesis.go +++ b/state/genesis.go @@ -100,9 +100,14 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { } } + // Make namereg tree + nameReg := merkle.NewIAVLTree(binary.BasicCodec, NameRegCodec, 0, db) + // TODO: add names to genesis.json + // IAVLTrees must be persisted before copy operations. accounts.Save() validatorInfos.Save() + nameReg.Save() return &State{ DB: db, @@ -116,5 +121,6 @@ func MakeGenesisState(db dbm.DB, genDoc *GenesisDoc) *State { UnbondingValidators: NewValidatorSet(nil), accounts: accounts, validatorInfos: validatorInfos, + nameReg: nameReg, } } diff --git a/state/state.go b/state/state.go index 13bd7d74d..e5bc863ea 100644 --- a/state/state.go +++ b/state/state.go @@ -3,6 +3,7 @@ package state import ( "bytes" "fmt" + "io" "time" "github.com/tendermint/tendermint/account" @@ -36,6 +37,7 @@ type State struct { UnbondingValidators *ValidatorSet accounts merkle.Tree // Shouldn't be accessed directly. validatorInfos merkle.Tree // Shouldn't be accessed directly. + nameReg merkle.Tree // Shouldn't be accessed directly. evc events.Fireable // typically an events.EventCache } @@ -61,6 +63,9 @@ func LoadState(db dbm.DB) *State { validatorInfosHash := binary.ReadByteSlice(r, n, err) s.validatorInfos = merkle.NewIAVLTree(binary.BasicCodec, ValidatorInfoCodec, 0, db) s.validatorInfos.Load(validatorInfosHash) + nameRegHash := binary.ReadByteSlice(r, n, err) + s.nameReg = merkle.NewIAVLTree(binary.BasicCodec, NameRegCodec, 0, db) + s.nameReg.Load(nameRegHash) if *err != nil { panic(*err) } @@ -72,6 +77,7 @@ func LoadState(db dbm.DB) *State { func (s *State) Save() { s.accounts.Save() s.validatorInfos.Save() + s.nameReg.Save() buf, n, err := new(bytes.Buffer), new(int64), new(error) binary.WriteString(s.ChainID, buf, n, err) binary.WriteUvarint(s.LastBlockHeight, buf, n, err) @@ -83,6 +89,7 @@ func (s *State) Save() { binary.WriteBinary(s.UnbondingValidators, buf, n, err) binary.WriteByteSlice(s.accounts.Hash(), buf, n, err) binary.WriteByteSlice(s.validatorInfos.Hash(), buf, n, err) + binary.WriteByteSlice(s.nameReg.Hash(), buf, n, err) if *err != nil { panic(*err) } @@ -105,6 +112,7 @@ func (s *State) Copy() *State { UnbondingValidators: s.UnbondingValidators.Copy(), // copy the valSet lazily. accounts: s.accounts.Copy(), validatorInfos: s.validatorInfos.Copy(), + nameReg: s.nameReg.Copy(), evc: nil, } } @@ -116,6 +124,7 @@ func (s *State) Hash() []byte { s.UnbondingValidators, s.accounts, s.validatorInfos, + s.nameReg, } return merkle.HashFromHashables(hashables) } @@ -272,6 +281,46 @@ func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) { // State.storage //------------------------------------- +// State.nameReg + +func (s *State) GetNameRegEntry(name []byte) *types.NameRegEntry { + _, value := s.nameReg.Get(name) + if value == nil { + return nil + } + entry := value.(*types.NameRegEntry) + // XXX: do we need to copy? + return entry +} + +func (s *State) UpdateNameRegEntry(entry *types.NameRegEntry) bool { + return s.nameReg.Set(entry.Name, entry) +} + +func (s *State) RemoveNameRegEntry(name []byte) bool { + _, removed := s.nameReg.Remove(name) + return removed +} + +func (s *State) GetNames() merkle.Tree { + return s.nameReg.Copy() +} + +func NameRegEncoder(o interface{}, w io.Writer, n *int64, err *error) { + binary.WriteBinary(o.(*types.NameRegEntry), w, n, err) +} + +func NameRegDecoder(r io.Reader, n *int64, err *error) interface{} { + return binary.ReadBinary(&types.NameRegEntry{}, r, n, err) +} + +var NameRegCodec = binary.Codec{ + Encode: NameRegEncoder, + Decode: NameRegDecoder, +} + +// State.nameReg +//------------------------------------- // Implements events.Eventable. Typically uses events.EventCache func (s *State) SetFireable(evc events.Fireable) { diff --git a/state/state_test.go b/state/state_test.go index a2fb2b78a..6804ca0c4 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -268,6 +268,91 @@ func TestTxs(t *testing.T) { } } + // CallTx. + { + state := state.Copy() + newAcc1 := state.GetAccount(acc1.Address) + newAcc1.Code = []byte{0x60} + state.UpdateAccount(newAcc1) + tx := &types.CallTx{ + Input: &types.TxInput{ + Address: acc0.Address, + Amount: 1, + Sequence: acc0.Sequence + 1, + PubKey: acc0PubKey, + }, + Address: acc1.Address, + GasLimit: 10, + } + + tx.Input.Signature = privAccounts[0].Sign(tx) + err := execTxWithState(state, tx, true) + if err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } + newAcc0 := state.GetAccount(acc0.Address) + if acc0.Balance-1 != newAcc0.Balance { + t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", + acc0.Balance-1, newAcc0.Balance) + } + newAcc1 = state.GetAccount(acc1.Address) + if acc1.Balance+1 != newAcc1.Balance { + t.Errorf("Unexpected newAcc1 balance. Expected %v, got %v", + acc1.Balance+1, newAcc1.Balance) + } + } + + // NameTx. + { + entryName := []byte("satoshi") + entryData := []byte(` +A purely peer-to-peer version of electronic cash would allow online +payments to be sent directly from one party to another without going through a +financial institution. Digital signatures provide part of the solution, but the main +benefits are lost if a trusted third party is still required to prevent double-spending. +We propose a solution to the double-spending problem using a peer-to-peer network. +The network timestamps transactions by hashing them into an ongoing chain of +hash-based proof-of-work, forming a record that cannot be changed without redoing +the proof-of-work. The longest chain not only serves as proof of the sequence of +events witnessed, but proof that it came from the largest pool of CPU power. As +long as a majority of CPU power is controlled by nodes that are not cooperating to +attack the network, they'll generate the longest chain and outpace attackers. The +network itself requires minimal structure. Messages are broadcast on a best effort +basis, and nodes can leave and rejoin the network at will, accepting the longest +proof-of-work chain as proof of what happened while they were gone `) + entryAmount := uint64(10000) + + state := state.Copy() + tx := &types.NameTx{ + Input: &types.TxInput{ + Address: acc0.Address, + Amount: entryAmount, + Sequence: acc0.Sequence + 1, + PubKey: acc0PubKey, + }, + Name: entryName, + Data: entryData, + } + + tx.Input.Signature = privAccounts[0].Sign(tx) + err := execTxWithState(state, tx, true) + if err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } + newAcc0 := state.GetAccount(acc0.Address) + if acc0.Balance-entryAmount != newAcc0.Balance { + t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", + acc0.Balance-entryAmount, newAcc0.Balance) + } + entry := state.GetNameRegEntry(entryName) + if entry == nil { + t.Errorf("Expected an entry but got nil") + } + if bytes.Compare(entry.Data, entryData) != 0 { + t.Errorf("Wrong data stored") + } + } + // BondTx. { state := state.Copy() diff --git a/types/names.go b/types/names.go new file mode 100644 index 000000000..2fbb16da8 --- /dev/null +++ b/types/names.go @@ -0,0 +1,8 @@ +package types + +type NameRegEntry struct { + Name []byte // registered name for the entry + Owner []byte // address that created the entry + Data []byte // binary encoded byte array + Expires uint // block at which this entry expires +} diff --git a/types/tx.go b/types/tx.go index 2d365881f..06f90d796 100644 --- a/types/tx.go +++ b/types/tx.go @@ -35,6 +35,7 @@ Tx (Transaction) is an atomic operation on the ledger state. Account Txs: - SendTx Send coins to address - CallTx Send a msg to a contract that runs in the vm + - NameTx Store some value under a name in the global namereg Validation Txs: - BondTx New validator posts a bond @@ -50,6 +51,7 @@ const ( // Account transactions TxTypeSend = byte(0x01) TxTypeCall = byte(0x02) + TxTypeName = byte(0x03) // Validation transactions TxTypeBond = byte(0x11) @@ -63,6 +65,7 @@ var _ = binary.RegisterInterface( struct{ Tx }{}, binary.ConcreteType{&SendTx{}, TxTypeSend}, binary.ConcreteType{&CallTx{}, TxTypeCall}, + binary.ConcreteType{&NameTx{}, TxTypeName}, binary.ConcreteType{&BondTx{}, TxTypeBond}, binary.ConcreteType{&UnbondTx{}, TxTypeUnbond}, binary.ConcreteType{&RebondTx{}, TxTypeRebond}, @@ -178,6 +181,28 @@ func (tx *CallTx) String() string { //----------------------------------------------------------------------------- +type NameTx struct { + Input *TxInput `json:"input"` + Name []byte `json:"name"` + Data []byte `json:"data"` + Fee uint64 `json:"fee"` +} + +func (tx *NameTx) WriteSignBytes(w io.Writer, n *int64, err *error) { + // We hex encode the network name so we don't deal with escaping issues. + binary.WriteTo([]byte(Fmt(`{"network":"%X"`, config.GetString("network"))), w, n, err) + binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"name":"%s","data":"%s"`, TxTypeName, tx.Name, tx.Data)), w, n, err) + binary.WriteTo([]byte(Fmt(`,"fee":%v,"input":`, tx.Fee)), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + binary.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *NameTx) String() string { + return Fmt("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data) +} + +//----------------------------------------------------------------------------- + type BondTx struct { PubKey account.PubKeyEd25519 `json:"pub_key"` Signature account.SignatureEd25519 `json:"signature"` From baaa69d7f8c9b6fcbb79366da0cc2d94b2c3adc2 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 22 May 2015 17:03:22 -0400 Subject: [PATCH 02/13] namereg rpc and tests --- rpc/core/names.go | 17 +++++++++ rpc/core/routes.go | 1 + rpc/core/types/responses.go | 4 +++ rpc/core_client/client.go | 3 +- rpc/core_client/client_methods.go | 58 +++++++++++++++++++++++++++++++ rpc/test/client_rpc_test.go | 8 +++++ rpc/test/helpers.go | 17 +++++++++ rpc/test/tests.go | 30 ++++++++++++++++ types/tx_utils.go | 37 ++++++++++++++++++++ 9 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 rpc/core/names.go diff --git a/rpc/core/names.go b/rpc/core/names.go new file mode 100644 index 000000000..928afb152 --- /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 f65d58e23..198c76f64 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 9239695f2..f9ac1a10a 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 3254aad27..47ec5c1e4 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 aa48dcffd..cf3804dcd 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 0ced2e664..2d9af88ae 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 da05ccfdc..bea0253df 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 ca9221ea3..8f6359d4b 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 eb5491ad1..beae2e861 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 From cff6bcfb31089b5771aa244438618d9c39658552 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 23 May 2015 02:05:56 -0400 Subject: [PATCH 03/13] copy entry on get, use strings for name/data, uint64 for expires --- rpc/core/names.go | 2 +- rpc/core_client/client_methods.go | 6 +++--- rpc/test/helpers.go | 4 ++-- rpc/test/tests.go | 6 +++--- state/block_cache.go | 16 ++++++++-------- state/execution.go | 25 +++++++++++++++---------- state/state.go | 7 +++---- state/state_test.go | 17 +++++++++++++---- types/names.go | 13 +++++++++---- types/tx.go | 17 +++++++++++++++-- types/tx_utils.go | 4 ++-- 11 files changed, 74 insertions(+), 43 deletions(-) diff --git a/rpc/core/names.go b/rpc/core/names.go index 928afb152..b73662d29 100644 --- a/rpc/core/names.go +++ b/rpc/core/names.go @@ -7,7 +7,7 @@ import ( ) // XXX: need we be careful about rendering bytes as string or is that their problem ? -func NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) { +func NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) { st := consensusState.GetState() // performs a copy entry := st.GetNameRegEntry(name) if entry == nil { diff --git a/rpc/core_client/client_methods.go b/rpc/core_client/client_methods.go index cf3804dcd..a5cbefbd4 100644 --- a/rpc/core_client/client_methods.go +++ b/rpc/core_client/client_methods.go @@ -28,7 +28,7 @@ type Client interface { ListAccounts() (*ctypes.ResponseListAccounts, error) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) ListValidators() (*ctypes.ResponseListValidators, error) - NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) + NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) NetInfo() (*ctypes.ResponseNetInfo, error) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseSignTx, error) Status() (*ctypes.ResponseStatus, error) @@ -454,7 +454,7 @@ func (c *ClientHTTP) ListValidators() (*ctypes.ResponseListValidators, error) { return response.Result, nil } -func (c *ClientHTTP) NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) { +func (c *ClientHTTP) NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) { values, err := argsToURLValues([]string{"name"}, name) if err != nil { return nil, err @@ -952,7 +952,7 @@ func (c *ClientJSON) ListValidators() (*ctypes.ResponseListValidators, error) { return response.Result, nil } -func (c *ClientJSON) NameRegEntry(name []byte) (*ctypes.ResponseNameRegEntry, error) { +func (c *ClientJSON) NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["NameRegEntry"], diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index bea0253df..5e01a30ce 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -120,7 +120,7 @@ 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 { +func makeDefaultNameTx(t *testing.T, typ string, name, value string, 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]) @@ -218,7 +218,7 @@ 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 { +func getNameRegEntry(t *testing.T, typ string, name string) *types.NameRegEntry { client := clients[typ] r, err := client.NameRegEntry(name) if err != nil { diff --git a/rpc/test/tests.go b/rpc/test/tests.go index 8f6359d4b..19a5b1b50 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -202,8 +202,8 @@ func testNameReg(t *testing.T, typ string) { 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?") + name := "ye-old-domain-name-" + typ + data := "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) @@ -215,7 +215,7 @@ func testNameReg(t *testing.T, typ string) { entry := getNameRegEntry(t, typ, name) - if bytes.Compare(entry.Data, data) != 0 { + if entry.Data != data { t.Fatal(fmt.Sprintf("Got %s, expected %s", entry.Data, data)) } diff --git a/state/block_cache.go b/state/block_cache.go index 102d26253..53278aa19 100644 --- a/state/block_cache.go +++ b/state/block_cache.go @@ -128,15 +128,15 @@ func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) { //------------------------------------- // BlockCache.names -func (cache *BlockCache) GetNameRegEntry(name []byte) *types.NameRegEntry { - entry, removed, _ := cache.names[string(name)].unpack() +func (cache *BlockCache) GetNameRegEntry(name string) *types.NameRegEntry { + entry, removed, _ := cache.names[name].unpack() if removed { return nil } else if entry != nil { return entry } else { entry = cache.backend.GetNameRegEntry(name) - cache.names[string(name)] = nameInfo{entry, false, false} + cache.names[name] = nameInfo{entry, false, false} return entry } } @@ -144,22 +144,22 @@ func (cache *BlockCache) GetNameRegEntry(name []byte) *types.NameRegEntry { func (cache *BlockCache) UpdateNameRegEntry(entry *types.NameRegEntry) { name := entry.Name // SANITY CHECK - _, removed, _ := cache.names[string(name)].unpack() + _, removed, _ := cache.names[name].unpack() if removed { panic("UpdateNameRegEntry on a removed name") } // SANITY CHECK END - cache.names[string(name)] = nameInfo{entry, false, true} + cache.names[name] = nameInfo{entry, false, true} } -func (cache *BlockCache) RemoveNameRegEntry(name []byte) { +func (cache *BlockCache) RemoveNameRegEntry(name string) { // SANITY CHECK - _, removed, _ := cache.names[string(name)].unpack() + _, removed, _ := cache.names[name].unpack() if removed { panic("RemoveNameRegEntry on a removed entry") } // SANITY CHECK END - cache.names[string(name)] = nameInfo{nil, true, false} + cache.names[name] = nameInfo{nil, true, false} } // BlockCache.names diff --git a/state/execution.go b/state/execution.go index a9f3ca823..dab938f39 100644 --- a/state/execution.go +++ b/state/execution.go @@ -504,13 +504,18 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea return types.ErrTxInsufficientFunds } + // validate the input strings + if !tx.Validate() { + log.Debug(Fmt("Invalid characters present in NameTx name (%s) or data (%s)", tx.Name, tx.Data)) + return types.ErrTxInvalidString + } + value := tx.Input.Amount - tx.Fee // let's say cost of a name for one block is len(data) + 32 - // TODO: the casting is dangerous and things can overflow (below)! - // we should make LastBlockHeight a uint64 - costPerBlock := uint(len(tx.Data) + 32) - expiresIn := uint(value / uint64(costPerBlock)) + costPerBlock := uint64(len(tx.Data) + 32) + expiresIn := value / uint64(costPerBlock) + lastBlockHeight := uint64(_s.LastBlockHeight) // check if the name exists entry := blockCache.GetNameRegEntry(tx.Name) @@ -518,7 +523,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea if entry != nil { var expired bool // if the entry already exists, and hasn't expired, we must be owner - if entry.Expires > _s.LastBlockHeight { + if entry.Expires > lastBlockHeight { // ensure we are owner if bytes.Compare(entry.Owner, tx.Input.Address) != 0 { log.Debug(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name)) @@ -537,14 +542,14 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // update the entry by bumping the expiry // and changing the data if expired { - entry.Expires = _s.LastBlockHeight + expiresIn + entry.Expires = lastBlockHeight + expiresIn } else { // since the size of the data may have changed // we use the total amount of "credit" - credit := uint64((entry.Expires - _s.LastBlockHeight) * uint(len(entry.Data))) + credit := (entry.Expires - lastBlockHeight) * uint64(len(entry.Data)) credit += value - expiresIn = uint(credit) / costPerBlock - entry.Expires = _s.LastBlockHeight + expiresIn + expiresIn = credit / costPerBlock + entry.Expires = lastBlockHeight + expiresIn } entry.Data = tx.Data blockCache.UpdateNameRegEntry(entry) @@ -558,7 +563,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea Name: tx.Name, Owner: tx.Input.Address, Data: tx.Data, - Expires: _s.LastBlockHeight + expiresIn, + Expires: lastBlockHeight + expiresIn, } blockCache.UpdateNameRegEntry(entry) } diff --git a/state/state.go b/state/state.go index e5bc863ea..a36fbff21 100644 --- a/state/state.go +++ b/state/state.go @@ -283,21 +283,20 @@ func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) { //------------------------------------- // State.nameReg -func (s *State) GetNameRegEntry(name []byte) *types.NameRegEntry { +func (s *State) GetNameRegEntry(name string) *types.NameRegEntry { _, value := s.nameReg.Get(name) if value == nil { return nil } entry := value.(*types.NameRegEntry) - // XXX: do we need to copy? - return entry + return entry.Copy() } func (s *State) UpdateNameRegEntry(entry *types.NameRegEntry) bool { return s.nameReg.Set(entry.Name, entry) } -func (s *State) RemoveNameRegEntry(name []byte) bool { +func (s *State) RemoveNameRegEntry(name string) bool { _, removed := s.nameReg.Remove(name) return removed } diff --git a/state/state_test.go b/state/state_test.go index 6804ca0c4..3c4d70c73 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -304,8 +304,8 @@ func TestTxs(t *testing.T) { // NameTx. { - entryName := []byte("satoshi") - entryData := []byte(` + entryName := "satoshi" + entryData := ` A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution. Digital signatures provide part of the solution, but the main @@ -319,7 +319,7 @@ long as a majority of CPU power is controlled by nodes that are not cooperating attack the network, they'll generate the longest chain and outpace attackers. The network itself requires minimal structure. Messages are broadcast on a best effort basis, and nodes can leave and rejoin the network at will, accepting the longest -proof-of-work chain as proof of what happened while they were gone `) +proof-of-work chain as proof of what happened while they were gone ` entryAmount := uint64(10000) state := state.Copy() @@ -348,9 +348,18 @@ proof-of-work chain as proof of what happened while they were gone `) if entry == nil { t.Errorf("Expected an entry but got nil") } - if bytes.Compare(entry.Data, entryData) != 0 { + if entry.Data != entryData { t.Errorf("Wrong data stored") } + + // test a bad string + tx.Data = string([]byte{0, 1, 2, 3, 127, 128, 129, 200, 251}) + tx.Input.Sequence += 1 + tx.Input.Signature = privAccounts[0].Sign(tx) + err = execTxWithState(state, tx, true) + if err != types.ErrTxInvalidString { + t.Errorf("Expected invalid string error. Got: %s", err.Error()) + } } // BondTx. diff --git a/types/names.go b/types/names.go index 2fbb16da8..e72cc8e16 100644 --- a/types/names.go +++ b/types/names.go @@ -1,8 +1,13 @@ package types type NameRegEntry struct { - Name []byte // registered name for the entry - Owner []byte // address that created the entry - Data []byte // binary encoded byte array - Expires uint // block at which this entry expires + Name string `json:"name"` // registered name for the entry + Owner []byte `json:"owner"` // address that created the entry + Data string `json:"data"` // binary encoded byte array + Expires uint64 `json:"expires"` // block at which this entry expires +} + +func (entry *NameRegEntry) Copy() *NameRegEntry { + entryCopy := *entry + return &entryCopy } diff --git a/types/tx.go b/types/tx.go index 06f90d796..15a61cf89 100644 --- a/types/tx.go +++ b/types/tx.go @@ -3,6 +3,7 @@ package types import ( "errors" "io" + "regexp" "github.com/tendermint/tendermint/account" "github.com/tendermint/tendermint/binary" @@ -18,6 +19,7 @@ var ( ErrTxUnknownPubKey = errors.New("Error unknown pubkey") ErrTxInvalidPubKey = errors.New("Error invalid pubkey") ErrTxInvalidSignature = errors.New("Error invalid signature") + ErrTxInvalidString = errors.New("Error invalid string") ) type ErrTxInvalidSequence struct { @@ -183,8 +185,8 @@ func (tx *CallTx) String() string { type NameTx struct { Input *TxInput `json:"input"` - Name []byte `json:"name"` - Data []byte `json:"data"` + Name string `json:"name"` + Data string `json:"data"` Fee uint64 `json:"fee"` } @@ -197,6 +199,17 @@ func (tx *NameTx) WriteSignBytes(w io.Writer, n *int64, err *error) { binary.WriteTo([]byte(`}]}`), w, n, err) } +// alphanum, underscore, forward slash +var regexpAlphaNum, _ = regexp.Compile("^[a-zA-Z0-9_/]*$") + +// anything you might find in a json +var regexpJSON, err = regexp.Compile(`^[a-zA-Z0-9_/ \-"':,\n\t.{}()\[\]]*$`) + +func (tx *NameTx) Validate() bool { + // Name should be alphanum and Data should be like JSON + return regexpAlphaNum.Match([]byte(tx.Name)) && regexpJSON.Match([]byte(tx.Data)) +} + func (tx *NameTx) String() string { return Fmt("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data) } diff --git a/types/tx_utils.go b/types/tx_utils.go index beae2e861..a7228c7b4 100644 --- a/types/tx_utils.go +++ b/types/tx_utils.go @@ -106,7 +106,7 @@ func (tx *CallTx) Sign(chainID string, privAccount *account.PrivAccount) { //---------------------------------------------------------------------------- // NameTx interface for creating tx -func NewNameTx(st AccountGetter, from account.PubKey, name, data []byte, amt, fee uint64) (*NameTx, error) { +func NewNameTx(st AccountGetter, from account.PubKey, name, data string, amt, fee uint64) (*NameTx, error) { addr := from.Address() acc := st.GetAccount(addr) if acc == nil { @@ -117,7 +117,7 @@ func NewNameTx(st AccountGetter, from account.PubKey, name, data []byte, amt, fe return NewNameTxWithNonce(from, name, data, amt, fee, nonce), nil } -func NewNameTxWithNonce(from account.PubKey, name, data []byte, amt, fee, nonce uint64) *NameTx { +func NewNameTxWithNonce(from account.PubKey, name, data string, amt, fee, nonce uint64) *NameTx { addr := from.Address() input := &TxInput{ Address: addr, From 02aedaaefb1088861bebab5526906967e42b3332 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 23 May 2015 03:46:02 -0400 Subject: [PATCH 04/13] more cleanup rpc tests, testing namereg --- config/tendermint_test/config.go | 25 +++++++++++---- rpc/test/client_ws_test.go | 4 +-- rpc/test/helpers.go | 12 ++----- rpc/test/tests.go | 55 ++++++++++++++++++++------------ state/execution.go | 2 +- types/tx.go | 1 + 6 files changed, 59 insertions(+), 40 deletions(-) diff --git a/config/tendermint_test/config.go b/config/tendermint_test/config.go index 418f026dc..cd9a17b18 100644 --- a/config/tendermint_test/config.go +++ b/config/tendermint_test/config.go @@ -104,25 +104,38 @@ func defaultConfig(moniker string) (defaultConfig string) { return } +// priv keys generated deterministically eg rpc/tests/helpers.go var defaultGenesis = `{ "chain_id" : "tendermint_test", "accounts": [ { - "address": "1D7A91CB32F758A02EBB9BE1FB6F8DEE56F90D42", - "amount": 200000000 + "address": "E9B5D87313356465FAE33C406CE2C2979DE60BCB", + "amount": 200000000 }, { - "address": "AC89A6DDF4C309A89A2C4078CE409A5A7B282270", - "amount": 200000000 + "address": "DFE4AFFA4CEE17CD01CB9E061D77C3ECED29BD88", + "amount": 200000000 + }, + { + "address": "F60D30722E7B497FA532FB3207C3FB29C31B1992", + "amount": 200000000 + }, + { + "address": "336CB40A5EB92E496E19B74FDFF2BA017C877FD6", + "amount": 200000000 + }, + { + "address": "D218F0F439BF0384F6F5EF8D0F8B398D941BD1DC", + "amount": 200000000 } ], "validators": [ { - "pub_key": [1, "06FBAC4E285285D1D91FCBC7E91C780ADA11516F67462340B3980CE2B94940E8"], + "pub_key": [1, "583779C3BFA3F6C7E23C7D830A9C3D023A216B55079AD38BFED1207B94A19548"], "amount": 1000000, "unbond_to": [ { - "address": "1D7A91CB32F758A02EBB9BE1FB6F8DEE56F90D42", + "address": "E9B5D87313356465FAE33C406CE2C2979DE60BCB", "amount": 100000 } ] diff --git a/rpc/test/client_ws_test.go b/rpc/test/client_ws_test.go index 5f94306b6..a310871b4 100644 --- a/rpc/test/client_ws_test.go +++ b/rpc/test/client_ws_test.go @@ -50,7 +50,7 @@ func TestWSBlockchainGrowth(t *testing.T) { // send a transaction and validate the events from listening for both sender and receiver func TestWSSend(t *testing.T) { - toAddr := []byte{20, 143, 25, 63, 16, 177, 83, 29, 91, 91, 54, 23, 233, 46, 190, 121, 122, 34, 86, 54} + toAddr := user[1].Address amt := uint64(100) con := newWSCon(t) @@ -80,7 +80,7 @@ func TestWSDoubleFire(t *testing.T) { con.Close() }() 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} + toAddr := user[1].Address // broadcast the transaction, wait to hear about it waitForEvent(t, con, eid, true, func() { tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt) diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 5e01a30ce..768704cf2 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -2,7 +2,6 @@ package rpctest import ( "bytes" - "encoding/hex" "strconv" "testing" "time" @@ -29,8 +28,7 @@ var ( mempoolCount = 0 // make keys - userPriv = "C453604BD6480D5538B4C6FD2E3E314B5BCE518D75ADE4DA3DA85AB8ADFD819606FBAC4E285285D1D91FCBC7E91C780ADA11516F67462340B3980CE2B94940E8" - user = makeUsers(2) + user = makeUsers(5) chainID string @@ -40,6 +38,7 @@ var ( } ) +// deterministic account generation, synced with genesis file in config/tendermint_test/config.go func makeUsers(n int) []*account.PrivAccount { accounts := []*account.PrivAccount{} for i := 0; i < n; i++ { @@ -47,13 +46,6 @@ func makeUsers(n int) []*account.PrivAccount { user := account.GenPrivAccountFromSecret(secret) accounts = append(accounts, user) } - - // include our validator - var byteKey [64]byte - userPrivByteSlice, _ := hex.DecodeString(userPriv) - copy(byteKey[:], userPrivByteSlice) - privAcc := account.GenPrivAccountFromKey(byteKey) - accounts[0] = privAcc return accounts } diff --git a/rpc/test/tests.go b/rpc/test/tests.go index 19a5b1b50..641fdd696 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -9,6 +9,8 @@ import ( "testing" ) +var doNothing = func(eid string, b []byte) error { return nil } + func testStatus(t *testing.T, typ string) { client := clients[typ] resp, err := client.Status() @@ -44,13 +46,13 @@ func testGetAccount(t *testing.T, typ string) { func testSignedTx(t *testing.T, typ string) { 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} + toAddr := user[1].Address testOneSignTx(t, typ, toAddr, amt) - toAddr = []byte{20, 143, 24, 63, 16, 17, 83, 29, 90, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54} + toAddr = user[2].Address testOneSignTx(t, typ, toAddr, amt) - toAddr = []byte{0, 0, 4, 0, 0, 4, 0, 0, 4, 91, 52, 2, 0, 41, 190, 121, 122, 34, 86, 54} + toAddr = user[3].Address testOneSignTx(t, typ, toAddr, amt) } @@ -71,7 +73,7 @@ func testOneSignTx(t *testing.T, typ string, addr []byte, amt uint64) { func testBroadcastTx(t *testing.T, typ string) { 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} + toAddr := user[1].Address tx := makeDefaultSendTxSigned(t, typ, toAddr, amt) receipt := broadcastTx(t, typ, tx) if receipt.CreatesContract > 0 { @@ -120,10 +122,7 @@ func testGetStorage(t *testing.T, typ string) { } // allow it to get mined - waitForEvent(t, con, eid, true, func() { - }, func(eid string, b []byte) error { - return nil - }) + waitForEvent(t, con, eid, true, func() {}, doNothing) mempoolCount = 0 v := getStorage(t, typ, contractAddr, []byte{0x1}) @@ -179,10 +178,7 @@ func testCall(t *testing.T, typ string) { } // allow it to get mined - waitForEvent(t, con, eid, true, func() { - }, func(eid string, b []byte) error { - return nil - }) + waitForEvent(t, con, eid, true, func() {}, doNothing) mempoolCount = 0 // run a call through the contract @@ -192,6 +188,7 @@ func testCall(t *testing.T, typ string) { } func testNameReg(t *testing.T, typ string) { + client := clients[typ] con := newWSCon(t) eid := types.EventStringNewBlock() subscribe(t, con, eid) @@ -200,23 +197,39 @@ func testNameReg(t *testing.T, typ string) { con.Close() }() + // register a new name, check if its there amt, fee := uint64(6969), uint64(1000) // since entries ought to be unique and these run against different clients, we append the typ - name := "ye-old-domain-name-" + typ - data := "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?" + name := "ye_old_domain_name_" + typ + data := "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 - }) - + waitForEvent(t, con, eid, true, func() {}, doNothing) + mempoolCount = 0 entry := getNameRegEntry(t, typ, name) + if entry.Data != data { + t.Fatal(fmt.Sprintf("Got %s, expected %s", entry.Data, data)) + } + // update the data as the owner, make sure still there + data = "if not now, when" + tx = makeDefaultNameTx(t, typ, name, data, amt, fee) + broadcastTx(t, typ, tx) + waitForEvent(t, con, eid, true, func() {}, doNothing) + mempoolCount = 0 + entry = getNameRegEntry(t, typ, name) if entry.Data != data { t.Fatal(fmt.Sprintf("Got %s, expected %s", entry.Data, data)) } + // try to update as non owner, should fail + nonce := getNonce(t, typ, user[1].Address) + data2 := "this is not my beautiful house" + tx = types.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, nonce) + tx.Sign(user[1]) + _, err := client.BroadcastTx(tx) + if err == nil { + t.Fatal("Expected error on NameTx") + } } diff --git a/state/execution.go b/state/execution.go index dab938f39..73b4c62b5 100644 --- a/state/execution.go +++ b/state/execution.go @@ -527,7 +527,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // ensure we are owner if bytes.Compare(entry.Owner, tx.Input.Address) != 0 { log.Debug(Fmt("Sender %X is trying to update a name (%s) for which he is not owner", tx.Input.Address, tx.Name)) - return types.ErrTxInvalidAddress // (?) + return types.ErrIncorrectOwner } } else { expired = true diff --git a/types/tx.go b/types/tx.go index 15a61cf89..0799b770a 100644 --- a/types/tx.go +++ b/types/tx.go @@ -20,6 +20,7 @@ var ( ErrTxInvalidPubKey = errors.New("Error invalid pubkey") ErrTxInvalidSignature = errors.New("Error invalid signature") ErrTxInvalidString = errors.New("Error invalid string") + ErrIncorrectOwner = errors.New("Error incorrect owner") ) type ErrTxInvalidSequence struct { From 77ff09e173f12af6e2dcf6eabd7517fe075a5302 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 24 May 2015 14:41:42 -0400 Subject: [PATCH 05/13] namereg cleanup, tests --- rpc/test/tests.go | 36 ++++++++-- state/execution.go | 27 ++++++-- state/state_test.go | 155 ++++++++++++++++++++++++++++++++++++++------ types/names.go | 35 ++++++++++ types/tx.go | 30 ++++++--- 5 files changed, 242 insertions(+), 41 deletions(-) diff --git a/rpc/test/tests.go b/rpc/test/tests.go index 641fdd696..fba33b23c 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -197,30 +197,41 @@ func testNameReg(t *testing.T, typ string) { con.Close() }() + types.MinNameRegistrationPeriod = 1 + // register a new name, check if its there - amt, fee := uint64(6969), uint64(1000) // since entries ought to be unique and these run against different clients, we append the typ name := "ye_old_domain_name_" + typ - data := "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" + data := "if not now, when" + fee := uint64(1000) + numDesiredBlocks := uint64(2) + amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx := makeDefaultNameTx(t, typ, name, data, amt, fee) broadcastTx(t, typ, tx) + // commit block waitForEvent(t, con, eid, true, func() {}, doNothing) mempoolCount = 0 entry := getNameRegEntry(t, typ, name) if entry.Data != data { - t.Fatal(fmt.Sprintf("Got %s, expected %s", entry.Data, data)) + t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data)) + } + if bytes.Compare(entry.Owner, user[0].Address) != 0 { + t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[0].Address)) } // update the data as the owner, make sure still there - data = "if not now, when" + numDesiredBlocks = uint64(2) + data = "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" + amt = fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx = makeDefaultNameTx(t, typ, name, data, amt, fee) broadcastTx(t, typ, tx) + // commit block waitForEvent(t, con, eid, true, func() {}, doNothing) mempoolCount = 0 entry = getNameRegEntry(t, typ, name) if entry.Data != data { - t.Fatal(fmt.Sprintf("Got %s, expected %s", entry.Data, data)) + t.Fatal(fmt.Sprintf("Err on entry.Data: Got %s, expected %s", entry.Data, data)) } // try to update as non owner, should fail @@ -232,4 +243,19 @@ func testNameReg(t *testing.T, typ string) { if err == nil { t.Fatal("Expected error on NameTx") } + + // commit block + waitForEvent(t, con, eid, true, func() {}, doNothing) + + // now the entry should be expired, so we can update as non owner + _, err = client.BroadcastTx(tx) + waitForEvent(t, con, eid, true, func() {}, doNothing) + mempoolCount = 0 + entry = getNameRegEntry(t, typ, name) + if entry.Data != data2 { + t.Fatal(fmt.Sprintf("Error on entry.Data: Got %s, expected %s", entry.Data, data2)) + } + if bytes.Compare(entry.Owner, user[1].Address) != 0 { + t.Fatal(fmt.Sprintf("Err on entry.Owner: Got %s, expected %s", entry.Owner, user[1].Address)) + } } diff --git a/state/execution.go b/state/execution.go index 73b4c62b5..304a653ad 100644 --- a/state/execution.go +++ b/state/execution.go @@ -505,18 +505,20 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea } // validate the input strings - if !tx.Validate() { - log.Debug(Fmt("Invalid characters present in NameTx name (%s) or data (%s)", tx.Name, tx.Data)) + if err := tx.ValidateStrings(); err != nil { + log.Debug(err.Error()) return types.ErrTxInvalidString } value := tx.Input.Amount - tx.Fee // let's say cost of a name for one block is len(data) + 32 - costPerBlock := uint64(len(tx.Data) + 32) + costPerBlock := types.NameCostPerBlock * types.NameCostPerByte * tx.BaseEntryCost() expiresIn := value / uint64(costPerBlock) lastBlockHeight := uint64(_s.LastBlockHeight) + log.Debug("New NameTx", "value", value, "costPerBlock", costPerBlock, "expiresIn", expiresIn, "lastBlock", lastBlockHeight) + // check if the name exists entry := blockCache.GetNameRegEntry(tx.Name) @@ -537,26 +539,36 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea if value == 0 && len(tx.Data) == 0 { // maybe we reward you for telling us we can delete this crap // (owners if not expired, anyone if expired) + log.Debug("Removing namereg entry", "name", entry.Name) blockCache.RemoveNameRegEntry(entry.Name) } else { // update the entry by bumping the expiry // and changing the data if expired { + if expiresIn < types.MinNameRegistrationPeriod { + return fmt.Errorf("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod) + } entry.Expires = lastBlockHeight + expiresIn + entry.Owner = tx.Input.Address + log.Debug("An old namereg entry has expired and been reclaimed", "name", entry.Name, "expiresIn", expiresIn, "owner", entry.Owner) } else { // since the size of the data may have changed // we use the total amount of "credit" - credit := (entry.Expires - lastBlockHeight) * uint64(len(entry.Data)) - credit += value + oldCredit := (entry.Expires - lastBlockHeight) * types.BaseEntryCost(entry.Name, entry.Data) + credit := oldCredit + value expiresIn = credit / costPerBlock + if expiresIn < types.MinNameRegistrationPeriod { + return fmt.Errorf("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod) + } entry.Expires = lastBlockHeight + expiresIn + log.Debug("Updated namereg entry", "name", entry.Name, "expiresIn", expiresIn, "oldCredit", oldCredit, "value", value, "credit", credit) } entry.Data = tx.Data blockCache.UpdateNameRegEntry(entry) } } else { - if expiresIn < 5 { - return fmt.Errorf("Names must be registered for at least 5 blocks") + if expiresIn < types.MinNameRegistrationPeriod { + return fmt.Errorf("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod) } // entry does not exist, so create it entry = &types.NameRegEntry{ @@ -565,6 +577,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea Data: tx.Data, Expires: lastBlockHeight + expiresIn, } + log.Debug("Creating namereg entry", "name", entry.Name, "expiresIn", expiresIn) blockCache.UpdateNameRegEntry(entry) } diff --git a/state/state_test.go b/state/state_test.go index 3c4d70c73..0a562f7f8 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -21,6 +21,15 @@ func execTxWithState(state *State, tx types.Tx, runCall bool) error { } } +func execTxWithStateNewBlock(state *State, tx types.Tx, runCall bool) error { + if err := execTxWithState(state, tx, runCall); err != nil { + return err + } + + state.LastBlockHeight += 1 + return nil +} + func TestCopyState(t *testing.T) { // Generate a random state s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000) @@ -166,26 +175,6 @@ func TestTxSequence(t *testing.T) { acc0PubKey := privAccounts[0].PubKey acc1 := state.GetAccount(privAccounts[1].PubKey.Address()) - // Try executing a SendTx with various sequence numbers. - makeSendTx := func(sequence uint) *types.SendTx { - return &types.SendTx{ - Inputs: []*types.TxInput{ - &types.TxInput{ - Address: acc0.Address, - Amount: 1, - Sequence: sequence, - PubKey: acc0PubKey, - }, - }, - Outputs: []*types.TxOutput{ - &types.TxOutput{ - Address: acc1.Address, - Amount: 1, - }, - }, - } - } - // Test a variety of sequence numbers for the tx. // The tx should only pass when i == 1. for i := -1; i < 3; i++ { @@ -220,6 +209,130 @@ func TestTxSequence(t *testing.T) { } } +func TestNameTxs(t *testing.T) { + state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + + types.MinNameRegistrationPeriod = 5 + startingBlock := uint64(state.LastBlockHeight) + + // try some bad names. these should all fail + names := []string{"", "\n", "123#$%", "\x00", string([]byte{20, 40, 60, 80}), "baffledbythespectacleinallofthisyouseeehesaidwithouteyes", "no spaces please"} + data := "something about all this just doesn't feel right." + fee := uint64(1000) + numDesiredBlocks := uint64(5) + for _, name := range names { + amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) + tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[0]) + + if err := execTxWithState(state, tx, true); err == nil { + t.Fatalf("Expected invalid name error from %s", name) + } + } + + // try some bad data. these should all fail + name := "hold_it_chum" + datas := []string{"cold&warm", "!@#$%^&*()", "<<<>>>>", "because why would you ever need a ~ or a & or even a % in a json file? make your case and we'll talk"} + for _, data := range datas { + amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) + tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[0]) + + if err := execTxWithState(state, tx, true); err == nil { + t.Fatalf("Expected invalid data error from %s", data) + } + } + + validateEntry := func(t *testing.T, entry *types.NameRegEntry, name, data string, addr []byte, expires uint64) { + + if entry == nil { + t.Fatalf("Could not find name %s", name) + } + if bytes.Compare(entry.Owner, addr) != 0 { + t.Fatalf("Wrong owner. Got %X expected %X", entry.Owner, addr) + } + if data != entry.Data { + t.Fatalf("Wrong data. Got %s expected %s", entry.Data, data) + } + if name != entry.Name { + t.Fatalf("Wrong name. Got %s expected %s", entry.Name, name) + } + if expires != entry.Expires { + t.Fatalf("Wrong expiry. Got %d, expected %d", entry.Expires, expires) + } + } + + // try a good one, check data, owner, expiry + name = "looking_good/karaoke_bar" + data = "on this side of neptune there are 1234567890 people: first is OMNIVORE. Or is it. Ok this is pretty restrictive. No exclamations :(. Faces tho :')" + amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) + tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[0]) + if err := execTxWithState(state, tx, true); err != nil { + t.Fatal(err) + } + entry := state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks) + + // fail to update it as non-owner, in same block + tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[1]) + if err := execTxWithState(state, tx, true); err == nil { + t.Fatal("Expected error") + } + + // update it as owner, just to increase expiry, in same block + // NOTE: we have to resend the data or it will clear it (is this what we want?) + tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[0]) + if err := execTxWithStateNewBlock(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*2) + + // update it as owner, just to increase expiry, in next block + tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[0]) + if err := execTxWithStateNewBlock(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*3) + + // fail to update it as non-owner + state.LastBlockHeight = uint(entry.Expires - 1) + tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[1]) + if err := execTxWithState(state, tx, true); err == nil { + t.Fatal("Expected error") + } + + // once expires, non-owner succeeds + state.LastBlockHeight = uint(entry.Expires) + tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[1]) + if err := execTxWithState(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[1].Address, uint64(state.LastBlockHeight)+numDesiredBlocks) + + // update it as new owner, with new data (longer), but keep the expiry! + data = "In the beginning there was no thing, not even the beginning. It hadn't been here, no there, nor for that matter anywhere, not especially because it had not to even exist, let alone to not. Nothing especially odd about that." + oldCredit := amt - fee + numDesiredBlocks = 10 + amt = fee + (numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) - oldCredit) + tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[1]) + if err := execTxWithState(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[1].Address, uint64(state.LastBlockHeight)+numDesiredBlocks) + +} + // TODO: test overflows. // TODO: test for unbonding validators. func TestTxs(t *testing.T) { @@ -268,7 +381,7 @@ func TestTxs(t *testing.T) { } } - // CallTx. + // CallTx. Just runs through it and checks the transfer. See vm, rpc tests for more { state := state.Copy() newAcc1 := state.GetAccount(acc1.Address) diff --git a/types/names.go b/types/names.go index e72cc8e16..22d20a357 100644 --- a/types/names.go +++ b/types/names.go @@ -1,5 +1,40 @@ package types +import ( + "regexp" +) + +var ( + MinNameRegistrationPeriod uint64 = 5 + + // cost for storing a name for a block is + // CostPerBlock*CostPerByte*(len(data) + 32) + NameCostPerByte uint64 = 1 + NameCostPerBlock uint64 = 1 + + MaxNameLength = 32 + MaxDataLength = 1 << 16 + + // Name should be alphanum, underscore, slash + // Data should be anything permitted in JSON + regexpAlphaNum = regexp.MustCompile("^[a-zA-Z0-9_/]*$") + regexpJSON = regexp.MustCompile(`^[a-zA-Z0-9_/ \-"':,\n\t.{}()\[\]]*$`) +) + +// filter strings +func validateNameRegEntryName(name string) bool { + return regexpAlphaNum.Match([]byte(name)) +} + +func validateNameRegEntryData(data string) bool { + return regexpJSON.Match([]byte(data)) +} + +// base cost is "effective" number of bytes +func BaseEntryCost(name, data string) uint64 { + return uint64(len(data) + 32) +} + type NameRegEntry struct { Name string `json:"name"` // registered name for the entry Owner []byte `json:"owner"` // address that created the entry diff --git a/types/tx.go b/types/tx.go index 0799b770a..1665a3320 100644 --- a/types/tx.go +++ b/types/tx.go @@ -3,7 +3,6 @@ package types import ( "errors" "io" - "regexp" "github.com/tendermint/tendermint/account" "github.com/tendermint/tendermint/binary" @@ -200,15 +199,30 @@ func (tx *NameTx) WriteSignBytes(w io.Writer, n *int64, err *error) { binary.WriteTo([]byte(`}]}`), w, n, err) } -// alphanum, underscore, forward slash -var regexpAlphaNum, _ = regexp.Compile("^[a-zA-Z0-9_/]*$") +func (tx *NameTx) ValidateStrings() error { + if len(tx.Name) == 0 { + return errors.New("Name must not be empty") + } + if len(tx.Name) > MaxNameLength { + return errors.New(Fmt("Name is too long. Max %d bytes", MaxNameLength)) + } + if len(tx.Data) > MaxDataLength { + return errors.New(Fmt("Data is too long. Max %d bytes", MaxDataLength)) + } + + if !validateNameRegEntryName(tx.Name) { + return errors.New(Fmt("Invalid characters found in NameTx.Name (%s). Only alphanumeric, underscores, and forward slashes allowed", tx.Name)) + } -// anything you might find in a json -var regexpJSON, err = regexp.Compile(`^[a-zA-Z0-9_/ \-"':,\n\t.{}()\[\]]*$`) + if !validateNameRegEntryData(tx.Data) { + return errors.New(Fmt("Invalid characters found in NameTx.Data (%s). Only the kind of things found in a JSON file are allowed", tx.Data)) + } + + return nil +} -func (tx *NameTx) Validate() bool { - // Name should be alphanum and Data should be like JSON - return regexpAlphaNum.Match([]byte(tx.Name)) && regexpJSON.Match([]byte(tx.Data)) +func (tx *NameTx) BaseEntryCost() uint64 { + return BaseEntryCost(tx.Name, tx.Data) } func (tx *NameTx) String() string { From 37a8a6cd651aab13f7bd1ff7dd77dd1cefd3fe2c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 May 2015 11:10:30 -0400 Subject: [PATCH 06/13] remove name fix --- rpc/core/names.go | 1 - state/block_cache.go | 5 ++--- state/state_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/rpc/core/names.go b/rpc/core/names.go index b73662d29..5c238b441 100644 --- a/rpc/core/names.go +++ b/rpc/core/names.go @@ -6,7 +6,6 @@ import ( 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 string) (*ctypes.ResponseNameRegEntry, error) { st := consensusState.GetState() // performs a copy entry := st.GetNameRegEntry(name) diff --git a/state/block_cache.go b/state/block_cache.go index 53278aa19..8cc4ccb08 100644 --- a/state/block_cache.go +++ b/state/block_cache.go @@ -245,7 +245,6 @@ func (cache *BlockCache) Sync() { // Determine order for names // note names may be of any length less than some limit - // and are arbitrary byte arrays... nameStrs := []string{} for nameStr := range cache.names { nameStrs = append(nameStrs, nameStr) @@ -256,9 +255,9 @@ func (cache *BlockCache) Sync() { for _, nameStr := range nameStrs { entry, removed, dirty := cache.names[nameStr].unpack() if removed { - removed := cache.backend.RemoveNameRegEntry(entry.Name) + removed := cache.backend.RemoveNameRegEntry(nameStr) if !removed { - panic(Fmt("Could not remove namereg entry to be removed: %X", entry.Name)) + panic(Fmt("Could not remove namereg entry to be removed: %s", nameStr)) } } else { if entry == nil { diff --git a/state/state_test.go b/state/state_test.go index 0a562f7f8..7e8ccc2db 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -331,6 +331,44 @@ func TestNameTxs(t *testing.T) { entry = state.GetNameRegEntry(name) validateEntry(t, entry, name, data, privAccounts[1].Address, uint64(state.LastBlockHeight)+numDesiredBlocks) + // test removal + amt = fee + data = "" + tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[1]) + if err := execTxWithStateNewBlock(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + if entry != nil { + t.Fatal("Expected removed entry to be nil") + } + + // create entry by key0, + // test removal by key1 after expiry + name = "looking_good/karaoke_bar" + data = "some data" + amt = fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) + tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[0]) + if err := execTxWithState(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + validateEntry(t, entry, name, data, privAccounts[0].Address, uint64(state.LastBlockHeight)+numDesiredBlocks) + state.LastBlockHeight = uint(entry.Expires) + + amt = fee + data = "" + tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) + tx.Sign(privAccounts[1]) + if err := execTxWithStateNewBlock(state, tx, true); err != nil { + t.Fatal(err) + } + entry = state.GetNameRegEntry(name) + if entry != nil { + t.Fatal("Expected removed entry to be nil") + } } // TODO: test overflows. From 19bd3bb2e2204c53a80c3efa1f9894af1a36b08b Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 27 May 2015 15:24:45 -0400 Subject: [PATCH 07/13] int/nonce fixes --- rpc/test/helpers.go | 8 ++++---- rpc/test/tests.go | 2 +- types/tx_utils.go | 40 ++++++++++++---------------------------- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 768704cf2..cbbb530e9 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -94,7 +94,7 @@ func init() { func makeDefaultSendTx(t *testing.T, typ string, addr []byte, amt uint64) *types.SendTx { nonce := getNonce(t, typ, user[0].Address) tx := types.NewSendTx() - tx.AddInputWithNonce(user[0].PubKey, amt, nonce) + tx.AddInputWithNonce(user[0].PubKey, amt, nonce+1) tx.AddOutput(addr, amt) return tx } @@ -114,7 +114,7 @@ func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, fee uint64) *types.NameTx { nonce := getNonce(t, typ, user[0].Address) - tx := types.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce) + tx := types.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce+1) tx.Sign(user[0]) return tx } @@ -123,7 +123,7 @@ func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, fee ui // rpc call wrappers (fail on err) // get an account's nonce -func getNonce(t *testing.T, typ string, addr []byte) uint64 { +func getNonce(t *testing.T, typ string, addr []byte) uint { client := clients[typ] ac, err := client.GetAccount(addr) if err != nil { @@ -132,7 +132,7 @@ func getNonce(t *testing.T, typ string, addr []byte) uint64 { if ac.Account == nil { return 0 } - return uint64(ac.Account.Sequence) + return ac.Account.Sequence } // get the account diff --git a/rpc/test/tests.go b/rpc/test/tests.go index fba33b23c..973a05b30 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -237,7 +237,7 @@ func testNameReg(t *testing.T, typ string) { // try to update as non owner, should fail nonce := getNonce(t, typ, user[1].Address) data2 := "this is not my beautiful house" - tx = types.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, nonce) + tx = types.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, nonce+1) tx.Sign(user[1]) _, err := client.BroadcastTx(tx) if err == nil { diff --git a/types/tx_utils.go b/types/tx_utils.go index a7228c7b4..39578f82c 100644 --- a/types/tx_utils.go +++ b/types/tx_utils.go @@ -25,23 +25,15 @@ func (tx *SendTx) AddInput(st AccountGetter, pubkey account.PubKey, amt uint64) if acc == nil { return fmt.Errorf("Invalid address %X from pubkey %X", addr, pubkey) } - - tx.Inputs = append(tx.Inputs, &TxInput{ - Address: addr, - Amount: amt, - Sequence: uint(acc.Sequence) + 1, - Signature: account.SignatureEd25519{}, - PubKey: pubkey, - }) - return nil + return tx.AddInputWithNonce(pubkey, amt, acc.Sequence+1) } -func (tx *SendTx) AddInputWithNonce(pubkey account.PubKey, amt, nonce uint64) error { +func (tx *SendTx) AddInputWithNonce(pubkey account.PubKey, amt uint64, nonce uint) error { addr := pubkey.Address() tx.Inputs = append(tx.Inputs, &TxInput{ Address: addr, Amount: amt, - Sequence: uint(nonce) + 1, + Sequence: nonce, Signature: account.SignatureEd25519{}, PubKey: pubkey, }) @@ -75,16 +67,16 @@ func NewCallTx(st AccountGetter, from account.PubKey, to, data []byte, amt, gasL return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from) } - nonce := uint64(acc.Sequence) + nonce := acc.Sequence + 1 return NewCallTxWithNonce(from, to, data, amt, gasLimit, fee, nonce), nil } -func NewCallTxWithNonce(from account.PubKey, to, data []byte, amt, gasLimit, fee, nonce uint64) *CallTx { +func NewCallTxWithNonce(from account.PubKey, to, data []byte, amt, gasLimit, fee uint64, nonce uint) *CallTx { addr := from.Address() input := &TxInput{ Address: addr, Amount: amt, - Sequence: uint(nonce) + 1, + Sequence: nonce, Signature: account.SignatureEd25519{}, PubKey: from, } @@ -113,16 +105,16 @@ func NewNameTx(st AccountGetter, from account.PubKey, name, data string, amt, fe return nil, fmt.Errorf("Invalid address %X from pubkey %X", addr, from) } - nonce := uint64(acc.Sequence) + nonce := acc.Sequence + 1 return NewNameTxWithNonce(from, name, data, amt, fee, nonce), nil } -func NewNameTxWithNonce(from account.PubKey, name, data string, amt, fee, nonce uint64) *NameTx { +func NewNameTxWithNonce(from account.PubKey, name, data string, amt, fee uint64, nonce uint) *NameTx { addr := from.Address() input := &TxInput{ Address: addr, Amount: amt, - Sequence: uint(nonce) + 1, + Sequence: nonce, Signature: account.SignatureEd25519{}, PubKey: from, } @@ -161,23 +153,15 @@ func (tx *BondTx) AddInput(st AccountGetter, pubkey account.PubKey, amt uint64) if acc == nil { return fmt.Errorf("Invalid address %X from pubkey %X", addr, pubkey) } - - tx.Inputs = append(tx.Inputs, &TxInput{ - Address: addr, - Amount: amt, - Sequence: uint(acc.Sequence) + 1, - Signature: account.SignatureEd25519{}, - PubKey: pubkey, - }) - return nil + return tx.AddInputWithNonce(pubkey, amt, acc.Sequence+1) } -func (tx *BondTx) AddInputWithNonce(pubkey account.PubKey, amt, nonce uint64) error { +func (tx *BondTx) AddInputWithNonce(pubkey account.PubKey, amt uint64, nonce uint) error { addr := pubkey.Address() tx.Inputs = append(tx.Inputs, &TxInput{ Address: addr, Amount: amt, - Sequence: uint(nonce) + 1, + Sequence: nonce, Signature: account.SignatureEd25519{}, PubKey: pubkey, }) From 6eb8386c7cbea7b65fbf56f7de149da7a438de98 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 30 May 2015 01:54:05 -0400 Subject: [PATCH 08/13] fixes for chain id in nametx sign functions --- rpc/test/helpers.go | 4 ++-- rpc/test/tests.go | 2 +- state/execution.go | 2 +- state/state_test.go | 34 ++++++++++++++++++---------------- types/tx.go | 4 ++-- types/tx_utils.go | 4 ++-- 6 files changed, 26 insertions(+), 24 deletions(-) diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index cbbb530e9..beab1cf02 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -107,7 +107,7 @@ func makeDefaultSendTxSigned(t *testing.T, typ string, addr []byte, amt uint64) func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, fee uint64) *types.CallTx { nonce := getNonce(t, typ, user[0].Address) - tx := types.NewCallTxWithNonce(user[0].PubKey, addr, code, amt, gasLim, fee, nonce) + tx := types.NewCallTxWithNonce(user[0].PubKey, addr, code, amt, gasLim, fee, nonce+1) tx.Sign(chainID, user[0]) return tx } @@ -115,7 +115,7 @@ func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, fee uint64) *types.NameTx { nonce := getNonce(t, typ, user[0].Address) tx := types.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce+1) - tx.Sign(user[0]) + tx.Sign(chainID, user[0]) return tx } diff --git a/rpc/test/tests.go b/rpc/test/tests.go index 973a05b30..35aafefb7 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -238,7 +238,7 @@ func testNameReg(t *testing.T, typ string) { nonce := getNonce(t, typ, user[1].Address) data2 := "this is not my beautiful house" tx = types.NewNameTxWithNonce(user[1].PubKey, name, data2, amt, fee, nonce+1) - tx.Sign(user[1]) + tx.Sign(chainID, user[1]) _, err := client.BroadcastTx(tx) if err == nil { t.Fatal("Expected error on NameTx") diff --git a/state/execution.go b/state/execution.go index 304a653ad..83cbea422 100644 --- a/state/execution.go +++ b/state/execution.go @@ -492,7 +492,7 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea log.Debug(Fmt("Can't find pubkey for %X", tx.Input.Address)) return err } - signBytes := account.SignBytes(tx) + signBytes := account.SignBytes(_s.ChainID, tx) err := validateInput(inAcc, signBytes, tx.Input) if err != nil { log.Debug(Fmt("validateInput failed on %X:", tx.Input.Address)) diff --git a/state/state_test.go b/state/state_test.go index 7e8ccc2db..acb809fbb 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -179,7 +179,9 @@ func TestTxSequence(t *testing.T) { // The tx should only pass when i == 1. for i := -1; i < 3; i++ { sequence := acc0.Sequence + uint(i) - tx := makeSendTx(sequence) + tx := types.NewSendTx() + tx.AddInputWithNonce(acc0PubKey, 1, sequence) + tx.AddOutput(acc1.Address, 1) tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) stateCopy := state.Copy() err := execTxWithState(stateCopy, tx, true) @@ -223,7 +225,7 @@ func TestNameTxs(t *testing.T) { for _, name := range names { amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[0]) + tx.Sign(state.ChainID, privAccounts[0]) if err := execTxWithState(state, tx, true); err == nil { t.Fatalf("Expected invalid name error from %s", name) @@ -236,7 +238,7 @@ func TestNameTxs(t *testing.T) { for _, data := range datas { amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[0]) + tx.Sign(state.ChainID, privAccounts[0]) if err := execTxWithState(state, tx, true); err == nil { t.Fatalf("Expected invalid data error from %s", data) @@ -267,7 +269,7 @@ func TestNameTxs(t *testing.T) { data = "on this side of neptune there are 1234567890 people: first is OMNIVORE. Or is it. Ok this is pretty restrictive. No exclamations :(. Faces tho :')" amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[0]) + tx.Sign(state.ChainID, privAccounts[0]) if err := execTxWithState(state, tx, true); err != nil { t.Fatal(err) } @@ -276,7 +278,7 @@ func TestNameTxs(t *testing.T) { // fail to update it as non-owner, in same block tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[1]) + tx.Sign(state.ChainID, privAccounts[1]) if err := execTxWithState(state, tx, true); err == nil { t.Fatal("Expected error") } @@ -284,7 +286,7 @@ func TestNameTxs(t *testing.T) { // update it as owner, just to increase expiry, in same block // NOTE: we have to resend the data or it will clear it (is this what we want?) tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[0]) + tx.Sign(state.ChainID, privAccounts[0]) if err := execTxWithStateNewBlock(state, tx, true); err != nil { t.Fatal(err) } @@ -293,7 +295,7 @@ func TestNameTxs(t *testing.T) { // update it as owner, just to increase expiry, in next block tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[0]) + tx.Sign(state.ChainID, privAccounts[0]) if err := execTxWithStateNewBlock(state, tx, true); err != nil { t.Fatal(err) } @@ -303,7 +305,7 @@ func TestNameTxs(t *testing.T) { // fail to update it as non-owner state.LastBlockHeight = uint(entry.Expires - 1) tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[1]) + tx.Sign(state.ChainID, privAccounts[1]) if err := execTxWithState(state, tx, true); err == nil { t.Fatal("Expected error") } @@ -311,7 +313,7 @@ func TestNameTxs(t *testing.T) { // once expires, non-owner succeeds state.LastBlockHeight = uint(entry.Expires) tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[1]) + tx.Sign(state.ChainID, privAccounts[1]) if err := execTxWithState(state, tx, true); err != nil { t.Fatal(err) } @@ -324,7 +326,7 @@ func TestNameTxs(t *testing.T) { numDesiredBlocks = 10 amt = fee + (numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) - oldCredit) tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[1]) + tx.Sign(state.ChainID, privAccounts[1]) if err := execTxWithState(state, tx, true); err != nil { t.Fatal(err) } @@ -335,7 +337,7 @@ func TestNameTxs(t *testing.T) { amt = fee data = "" tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[1]) + tx.Sign(state.ChainID, privAccounts[1]) if err := execTxWithStateNewBlock(state, tx, true); err != nil { t.Fatal(err) } @@ -350,7 +352,7 @@ func TestNameTxs(t *testing.T) { data = "some data" amt = fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[0]) + tx.Sign(state.ChainID, privAccounts[0]) if err := execTxWithState(state, tx, true); err != nil { t.Fatal(err) } @@ -361,7 +363,7 @@ func TestNameTxs(t *testing.T) { amt = fee data = "" tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(privAccounts[1]) + tx.Sign(state.ChainID, privAccounts[1]) if err := execTxWithStateNewBlock(state, tx, true); err != nil { t.Fatal(err) } @@ -436,7 +438,7 @@ func TestTxs(t *testing.T) { GasLimit: 10, } - tx.Input.Signature = privAccounts[0].Sign(tx) + tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) err := execTxWithState(state, tx, true) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) @@ -485,7 +487,7 @@ proof-of-work chain as proof of what happened while they were gone ` Data: entryData, } - tx.Input.Signature = privAccounts[0].Sign(tx) + tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) err := execTxWithState(state, tx, true) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) @@ -506,7 +508,7 @@ proof-of-work chain as proof of what happened while they were gone ` // test a bad string tx.Data = string([]byte{0, 1, 2, 3, 127, 128, 129, 200, 251}) tx.Input.Sequence += 1 - tx.Input.Signature = privAccounts[0].Sign(tx) + tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) err = execTxWithState(state, tx, true) if err != types.ErrTxInvalidString { t.Errorf("Expected invalid string error. Got: %s", err.Error()) diff --git a/types/tx.go b/types/tx.go index 1665a3320..8bb596407 100644 --- a/types/tx.go +++ b/types/tx.go @@ -190,9 +190,9 @@ type NameTx struct { Fee uint64 `json:"fee"` } -func (tx *NameTx) WriteSignBytes(w io.Writer, n *int64, err *error) { +func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { // We hex encode the network name so we don't deal with escaping issues. - binary.WriteTo([]byte(Fmt(`{"network":"%X"`, config.GetString("network"))), w, n, err) + binary.WriteTo([]byte(Fmt(`{"network":"%X"`, chainID)), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"name":"%s","data":"%s"`, TxTypeName, tx.Name, tx.Data)), w, n, err) binary.WriteTo([]byte(Fmt(`,"fee":%v,"input":`, tx.Fee)), w, n, err) tx.Input.WriteSignBytes(w, n, err) diff --git a/types/tx_utils.go b/types/tx_utils.go index 39578f82c..0c878e314 100644 --- a/types/tx_utils.go +++ b/types/tx_utils.go @@ -127,9 +127,9 @@ func NewNameTxWithNonce(from account.PubKey, name, data string, amt, fee uint64, } } -func (tx *NameTx) Sign(privAccount *account.PrivAccount) { +func (tx *NameTx) Sign(chainID string, privAccount *account.PrivAccount) { tx.Input.PubKey = privAccount.PubKey - tx.Input.Signature = privAccount.Sign(tx) + tx.Input.Signature = privAccount.Sign(chainID, tx) } //---------------------------------------------------------------------------- From 231a7330e2e71705097728e23587229d776f4bab Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 30 May 2015 11:13:09 -0400 Subject: [PATCH 09/13] list names rpc --- rpc/core/names.go | 17 +++++++++++++++-- rpc/core/routes.go | 3 ++- rpc/core/types/responses.go | 5 +++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/rpc/core/names.go b/rpc/core/names.go index 5c238b441..7f0d872ec 100644 --- a/rpc/core/names.go +++ b/rpc/core/names.go @@ -4,13 +4,26 @@ import ( "fmt" ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/types" ) -func NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) { +func GetName(name string) (*types.NameRegEntry, 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 + return entry, nil +} + +func ListNames() (*ctypes.ResponseListNames, error) { + var blockHeight uint + var names []*types.NameRegEntry + state := consensusState.GetState() + blockHeight = state.LastBlockHeight + state.GetNames().Iterate(func(key interface{}, value interface{}) bool { + names = append(names, value.(*types.NameRegEntry)) + return false + }) + return &ctypes.ResponseListNames{blockHeight, names}, nil } diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 198c76f64..6e8fe2499 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -20,7 +20,8 @@ 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"}), + "get_name": rpc.NewRPCFunc(GetName, []string{"name"}), + "list_names": rpc.NewRPCFunc(ListNames, []string{}), "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 f9ac1a10a..297a83b9a 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -101,6 +101,7 @@ type ResponseDumpConsensusState struct { PeerRoundStates []string `json:"peer_round_states"` } -type ResponseNameRegEntry struct { - Entry *types.NameRegEntry `json:"entry"` +type ResponseListNames struct { + BlockHeight uint `json:"block_height"` + Names []*types.NameRegEntry `json:"names"` } From 41502e05c1cb8e9b75c8dbd069de6a5c80c77381 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 30 May 2015 20:18:43 -0400 Subject: [PATCH 10/13] chain_id written as string not hex in WriteSignBytes --- consensus/types/proposal.go | 3 +-- consensus/types/proposal_test.go | 2 +- types/names.go | 2 +- types/tx.go | 18 ++++++------------ types/tx_test.go | 10 +++++----- types/vote.go | 3 +-- 6 files changed, 15 insertions(+), 23 deletions(-) diff --git a/consensus/types/proposal.go b/consensus/types/proposal.go index a306f032a..d2fb4b15f 100644 --- a/consensus/types/proposal.go +++ b/consensus/types/proposal.go @@ -39,8 +39,7 @@ func (p *Proposal) String() string { } func (p *Proposal) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - // We hex encode the chain_id name so we don't deal with escaping issues. - binary.WriteTo([]byte(Fmt(`{"chain_id":"%X"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) binary.WriteTo([]byte(`,"proposal":{"block_parts":`), w, n, err) p.BlockParts.WriteSignBytes(w, n, err) binary.WriteTo([]byte(Fmt(`,"height":%v,"pol_parts":`, p.Height)), w, n, err) diff --git a/consensus/types/proposal_test.go b/consensus/types/proposal_test.go index fa8f37be3..5cc4a5f38 100644 --- a/consensus/types/proposal_test.go +++ b/consensus/types/proposal_test.go @@ -19,7 +19,7 @@ func TestProposalSignable(t *testing.T) { } signBytes := account.SignBytes(config.GetString("chain_id"), proposal) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%X","proposal":{"block_parts":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_parts":{"hash":"706F6C7061727473","total":222},"round":23456}}`, + expected := Fmt(`{"chain_id":"%s","proposal":{"block_parts":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_parts":{"hash":"706F6C7061727473","total":222},"round":23456}}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) diff --git a/types/names.go b/types/names.go index 22d20a357..3e31991fa 100644 --- a/types/names.go +++ b/types/names.go @@ -38,7 +38,7 @@ func BaseEntryCost(name, data string) uint64 { type NameRegEntry struct { Name string `json:"name"` // registered name for the entry Owner []byte `json:"owner"` // address that created the entry - Data string `json:"data"` // binary encoded byte array + Data string `json:"data"` // data to store under this name Expires uint64 `json:"expires"` // block at which this entry expires } diff --git a/types/tx.go b/types/tx.go index 8bb596407..811ab1fd0 100644 --- a/types/tx.go +++ b/types/tx.go @@ -135,8 +135,7 @@ type SendTx struct { } func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - // We hex encode the chain_id so we don't deal with escaping issues. - binary.WriteTo([]byte(Fmt(`{"chain_id":"%X"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err) for i, in := range tx.Inputs { in.WriteSignBytes(w, n, err) @@ -169,8 +168,7 @@ type CallTx struct { } func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - // We hex encode the chain_id so we don't deal with escaping issues. - binary.WriteTo([]byte(Fmt(`{"chain_id":"%X"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err) binary.WriteTo([]byte(Fmt(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err) tx.Input.WriteSignBytes(w, n, err) @@ -191,8 +189,7 @@ type NameTx struct { } func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - // We hex encode the network name so we don't deal with escaping issues. - binary.WriteTo([]byte(Fmt(`{"network":"%X"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"name":"%s","data":"%s"`, TxTypeName, tx.Name, tx.Data)), w, n, err) binary.WriteTo([]byte(Fmt(`,"fee":%v,"input":`, tx.Fee)), w, n, err) tx.Input.WriteSignBytes(w, n, err) @@ -239,8 +236,7 @@ type BondTx struct { } func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - // We hex encode the chain_id so we don't deal with escaping issues. - binary.WriteTo([]byte(Fmt(`{"chain_id":"%X"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err) for i, in := range tx.Inputs { in.WriteSignBytes(w, n, err) @@ -273,8 +269,7 @@ type UnbondTx struct { } func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - // We hex encode the chain_id so we don't deal with escaping issues. - binary.WriteTo([]byte(Fmt(`{"chain_id":"%X"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err) } @@ -291,8 +286,7 @@ type RebondTx struct { } func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - // We hex encode the chain_id so we don't deal with escaping issues. - binary.WriteTo([]byte(Fmt(`{"chain_id":"%X"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err) } diff --git a/types/tx_test.go b/types/tx_test.go index 5ac1f908d..703f873ed 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -41,7 +41,7 @@ func TestSendTxSignable(t *testing.T) { } signBytes := account.SignBytes(chainID, sendTx) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%X","tx":[1,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"outputs":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`, + expected := Fmt(`{"chain_id":"%s","tx":[1,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"outputs":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) @@ -62,7 +62,7 @@ func TestCallTxSignable(t *testing.T) { } signBytes := account.SignBytes(chainID, callTx) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%X","tx":[2,{"address":"636F6E747261637431","data":"6461746131","fee":222,"gas_limit":111,"input":{"address":"696E70757431","amount":12345,"sequence":67890}}]}`, + expected := Fmt(`{"chain_id":"%s","tx":[2,{"address":"636F6E747261637431","data":"6461746131","fee":222,"gas_limit":111,"input":{"address":"696E70757431","amount":12345,"sequence":67890}}]}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for CallTx. Expected:\n%v\nGot:\n%v", expected, signStr) @@ -98,7 +98,7 @@ func TestBondTxSignable(t *testing.T) { } signBytes := account.SignBytes(chainID, bondTx) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%X","tx":[17,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"pub_key":[1,"3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29"],"unbond_to":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`, + expected := Fmt(`{"chain_id":"%s","tx":[17,{"inputs":[{"address":"696E70757431","amount":12345,"sequence":67890},{"address":"696E70757432","amount":111,"sequence":222}],"pub_key":[1,"3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29"],"unbond_to":[{"address":"6F757470757431","amount":333},{"address":"6F757470757432","amount":444}]}]}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for BondTx") @@ -112,7 +112,7 @@ func TestUnbondTxSignable(t *testing.T) { } signBytes := account.SignBytes(chainID, unbondTx) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%X","tx":[18,{"address":"6164647265737331","height":111}]}`, + expected := Fmt(`{"chain_id":"%s","tx":[18,{"address":"6164647265737331","height":111}]}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for UnbondTx") @@ -126,7 +126,7 @@ func TestRebondTxSignable(t *testing.T) { } signBytes := account.SignBytes(chainID, rebondTx) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%X","tx":[19,{"address":"6164647265737331","height":111}]}`, + expected := Fmt(`{"chain_id":"%s","tx":[19,{"address":"6164647265737331","height":111}]}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for RebondTx") diff --git a/types/vote.go b/types/vote.go index def488e92..365b5a9af 100644 --- a/types/vote.go +++ b/types/vote.go @@ -46,8 +46,7 @@ const ( ) func (vote *Vote) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - // We hex encode the chain_id name so we don't deal with escaping issues. - binary.WriteTo([]byte(Fmt(`{"chain_id":"%X"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) binary.WriteTo([]byte(Fmt(`,"vote":{"block_hash":"%X","block_parts":%v`, vote.BlockHash, vote.BlockParts)), w, n, err) binary.WriteTo([]byte(Fmt(`,"height":%v,"round":%v,"type":%v}}`, vote.Height, vote.Round, vote.Type)), w, n, err) } From 293aa31f64e17e42e7b46ed3be820efbe1f7899e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 30 May 2015 20:48:40 -0400 Subject: [PATCH 11/13] rpc-gen update --- rpc/core_client/client.go | 34 ++++---- rpc/core_client/client_methods.go | 136 +++++++++++++++++++++--------- rpc/test/helpers.go | 4 +- 3 files changed, 118 insertions(+), 56 deletions(-) diff --git a/rpc/core_client/client.go b/rpc/core_client/client.go index 47ec5c1e4..2af0ef0b5 100644 --- a/rpc/core_client/client.go +++ b/rpc/core_client/client.go @@ -16,21 +16,25 @@ import ( // maps camel-case function names to lower case rpc version var reverseFuncMap = map[string]string{ - "Status": "status", - "NetInfo": "net_info", - "BlockchainInfo": "blockchain", - "GetBlock": "get_block", - "GetAccount": "get_account", - "GetStorage": "get_storage", - "Call": "call", - "CallCode": "call_code", - "ListValidators": "list_validators", - "DumpStorage": "dump_storage", - "BroadcastTx": "broadcast_tx", - "ListAccounts": "list_accounts", - "NameRegEntry": "name_reg_entry", - "GenPrivAccount": "unsafe/gen_priv_account", - "SignTx": "unsafe/sign_tx", + "Status": "status", + "NetInfo": "net_info", + "BlockchainInfo": "blockchain", + "Genesis": "genesis", + "GetBlock": "get_block", + "GetAccount": "get_account", + "GetStorage": "get_storage", + "Call": "call", + "CallCode": "call_code", + "ListValidators": "list_validators", + "DumpConsensusState": "dump_consensus_state", + "DumpStorage": "dump_storage", + "BroadcastTx": "broadcast_tx", + "ListUnconfirmedTxs": "list_unconfirmed_txs", + "ListAccounts": "list_accounts", + "GetName": "get_name", + "ListNames": "list_names", + "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 a5cbefbd4..d560c68fd 100644 --- a/rpc/core_client/client_methods.go +++ b/rpc/core_client/client_methods.go @@ -24,11 +24,12 @@ type Client interface { Genesis() (*string, error) GetAccount(address []byte) (*ctypes.ResponseGetAccount, error) GetBlock(height uint) (*ctypes.ResponseGetBlock, error) + GetName(name string) (*types.NameRegEntry, error) GetStorage(address []byte, key []byte) (*ctypes.ResponseGetStorage, error) ListAccounts() (*ctypes.ResponseListAccounts, error) + ListNames() (*ctypes.ResponseListNames, error) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) ListValidators() (*ctypes.ResponseListValidators, error) - NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) NetInfo() (*ctypes.ResponseNetInfo, error) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseSignTx, error) Status() (*ctypes.ResponseStatus, error) @@ -334,6 +335,36 @@ func (c *ClientHTTP) GetBlock(height uint) (*ctypes.ResponseGetBlock, error) { return response.Result, nil } +func (c *ClientHTTP) GetName(name string) (*types.NameRegEntry, error) { + values, err := argsToURLValues([]string{"name"}, name) + if err != nil { + return nil, err + } + resp, err := http.PostForm(c.addr+reverseFuncMap["GetName"], 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 *types.NameRegEntry `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) GetStorage(address []byte, key []byte) (*ctypes.ResponseGetStorage, error) { values, err := argsToURLValues([]string{"address", "key"}, address, key) if err != nil { @@ -394,12 +425,12 @@ func (c *ClientHTTP) ListAccounts() (*ctypes.ResponseListAccounts, error) { return response.Result, nil } -func (c *ClientHTTP) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) { +func (c *ClientHTTP) ListNames() (*ctypes.ResponseListNames, error) { values, err := argsToURLValues(nil) if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+reverseFuncMap["ListUnconfirmedTxs"], values) + resp, err := http.PostForm(c.addr+reverseFuncMap["ListNames"], values) if err != nil { return nil, err } @@ -409,10 +440,10 @@ func (c *ClientHTTP) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, e return nil, err } var response struct { - Result *ctypes.ResponseListUnconfirmedTxs `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *ctypes.ResponseListNames `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -424,12 +455,12 @@ func (c *ClientHTTP) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, e return response.Result, nil } -func (c *ClientHTTP) ListValidators() (*ctypes.ResponseListValidators, error) { +func (c *ClientHTTP) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) { values, err := argsToURLValues(nil) if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+reverseFuncMap["ListValidators"], values) + resp, err := http.PostForm(c.addr+reverseFuncMap["ListUnconfirmedTxs"], values) if err != nil { return nil, err } @@ -439,10 +470,10 @@ func (c *ClientHTTP) ListValidators() (*ctypes.ResponseListValidators, error) { return nil, err } var response struct { - Result *ctypes.ResponseListValidators `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *ctypes.ResponseListUnconfirmedTxs `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -454,12 +485,12 @@ func (c *ClientHTTP) ListValidators() (*ctypes.ResponseListValidators, error) { return response.Result, nil } -func (c *ClientHTTP) NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) { - values, err := argsToURLValues([]string{"name"}, name) +func (c *ClientHTTP) ListValidators() (*ctypes.ResponseListValidators, error) { + values, err := argsToURLValues(nil) if err != nil { return nil, err } - resp, err := http.PostForm(c.addr+reverseFuncMap["NameRegEntry"], values) + resp, err := http.PostForm(c.addr+reverseFuncMap["ListValidators"], values) if err != nil { return nil, err } @@ -469,10 +500,10 @@ func (c *ClientHTTP) NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, er return nil, err } var response struct { - Result *ctypes.ResponseNameRegEntry `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *ctypes.ResponseListValidators `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -844,6 +875,33 @@ func (c *ClientJSON) GetBlock(height uint) (*ctypes.ResponseGetBlock, error) { return response.Result, nil } +func (c *ClientJSON) GetName(name string) (*types.NameRegEntry, error) { + request := rpctypes.RPCRequest{ + JSONRPC: "2.0", + Method: reverseFuncMap["GetName"], + Params: []interface{}{name}, + Id: 0, + } + body, err := c.RequestResponse(request) + if err != nil { + return nil, err + } + var response struct { + Result *types.NameRegEntry `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) GetStorage(address []byte, key []byte) (*ctypes.ResponseGetStorage, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", @@ -898,10 +956,10 @@ func (c *ClientJSON) ListAccounts() (*ctypes.ResponseListAccounts, error) { return response.Result, nil } -func (c *ClientJSON) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) { +func (c *ClientJSON) ListNames() (*ctypes.ResponseListNames, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", - Method: reverseFuncMap["ListUnconfirmedTxs"], + Method: reverseFuncMap["ListNames"], Params: []interface{}{}, Id: 0, } @@ -910,10 +968,10 @@ func (c *ClientJSON) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, e return nil, err } var response struct { - Result *ctypes.ResponseListUnconfirmedTxs `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *ctypes.ResponseListNames `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -925,10 +983,10 @@ func (c *ClientJSON) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, e return response.Result, nil } -func (c *ClientJSON) ListValidators() (*ctypes.ResponseListValidators, error) { +func (c *ClientJSON) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", - Method: reverseFuncMap["ListValidators"], + Method: reverseFuncMap["ListUnconfirmedTxs"], Params: []interface{}{}, Id: 0, } @@ -937,10 +995,10 @@ func (c *ClientJSON) ListValidators() (*ctypes.ResponseListValidators, error) { return nil, err } var response struct { - Result *ctypes.ResponseListValidators `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *ctypes.ResponseListUnconfirmedTxs `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -952,11 +1010,11 @@ func (c *ClientJSON) ListValidators() (*ctypes.ResponseListValidators, error) { return response.Result, nil } -func (c *ClientJSON) NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, error) { +func (c *ClientJSON) ListValidators() (*ctypes.ResponseListValidators, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", - Method: reverseFuncMap["NameRegEntry"], - Params: []interface{}{name}, + Method: reverseFuncMap["ListValidators"], + Params: []interface{}{}, Id: 0, } body, err := c.RequestResponse(request) @@ -964,10 +1022,10 @@ func (c *ClientJSON) NameRegEntry(name string) (*ctypes.ResponseNameRegEntry, er return nil, err } var response struct { - Result *ctypes.ResponseNameRegEntry `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *ctypes.ResponseListValidators `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index beab1cf02..370c44009 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -212,11 +212,11 @@ func callContract(t *testing.T, client cclient.Client, address, data, expected [ // get the namereg entry func getNameRegEntry(t *testing.T, typ string, name string) *types.NameRegEntry { client := clients[typ] - r, err := client.NameRegEntry(name) + entry, err := client.GetName(name) if err != nil { t.Fatal(err) } - return r.Entry + return entry } //-------------------------------------------------------------------------------- From ec282d3e3dd653edcbb57e3789bfa0c2087e1498 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 1 Jun 2015 13:51:03 -0400 Subject: [PATCH 12/13] rpc: remove unecessary response wrappers --- rpc/core/accounts.go | 8 +-- rpc/core/mempool.go | 10 ++- rpc/core/txs.go | 4 +- rpc/core/types/responses.go | 20 ------ rpc/core_client/client_methods.go | 111 +++++++++++++++--------------- rpc/test/helpers.go | 16 ++--- rpc/test/tests.go | 4 +- 7 files changed, 76 insertions(+), 97 deletions(-) diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go index ba43673a0..e102e1b72 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -7,11 +7,11 @@ import ( ctypes "github.com/tendermint/tendermint/rpc/core/types" ) -func GenPrivAccount() (*ctypes.ResponseGenPrivAccount, error) { - return &ctypes.ResponseGenPrivAccount{acm.GenPrivAccount()}, nil +func GenPrivAccount() (*acm.PrivAccount, error) { + return acm.GenPrivAccount(), nil } -func GetAccount(address []byte) (*ctypes.ResponseGetAccount, error) { +func GetAccount(address []byte) (*acm.Account, error) { cache := mempoolReactor.Mempool.GetCache() account := cache.GetAccount(address) if account == nil { @@ -24,7 +24,7 @@ func GetAccount(address []byte) (*ctypes.ResponseGetAccount, error) { StorageRoot: nil, } } - return &ctypes.ResponseGetAccount{account}, nil + return account, nil } func GetStorage(address, key []byte) (*ctypes.ResponseGetStorage, error) { diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index e4ae77096..c9e83b3df 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -9,9 +9,8 @@ import ( //----------------------------------------------------------------------------- -// pass pointer? // Note: tx must be signed -func BroadcastTx(tx types.Tx) (*ctypes.ResponseBroadcastTx, error) { +func BroadcastTx(tx types.Tx) (*ctypes.Receipt, error) { err := mempoolReactor.BroadcastTx(tx) if err != nil { return nil, fmt.Errorf("Error broadcasting transaction: %v", err) @@ -27,10 +26,9 @@ func BroadcastTx(tx types.Tx) (*ctypes.ResponseBroadcastTx, error) { contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence)) } } - return &ctypes.ResponseBroadcastTx{ctypes.Receipt{txHash, createsContract, contractAddr}}, nil + return &ctypes.Receipt{txHash, createsContract, contractAddr}, nil } -func ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) { - txs := mempoolReactor.Mempool.GetProposalTxs() - return &ctypes.ResponseListUnconfirmedTxs{txs}, nil +func ListUnconfirmedTxs() ([]types.Tx, error) { + return mempoolReactor.Mempool.GetProposalTxs(), nil } diff --git a/rpc/core/txs.go b/rpc/core/txs.go index 07e14d94d..be269f470 100644 --- a/rpc/core/txs.go +++ b/rpc/core/txs.go @@ -78,7 +78,7 @@ func CallCode(code, data []byte) (*ctypes.ResponseCall, error) { //----------------------------------------------------------------------------- -func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseSignTx, error) { +func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error) { // more checks? for i, privAccount := range privAccounts { @@ -113,5 +113,5 @@ func SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseS rebondTx := tx.(*types.RebondTx) rebondTx.Signature = privAccounts[0].Sign(config.GetString("chain_id"), rebondTx).(account.SignatureEd25519) } - return &ctypes.ResponseSignTx{tx}, nil + return tx, nil } diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 297a83b9a..221b95628 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -6,14 +6,6 @@ import ( "github.com/tendermint/tendermint/types" ) -type ResponseGenPrivAccount struct { - PrivAccount *account.PrivAccount `json:"priv_account"` -} - -type ResponseGetAccount struct { - Account *account.Account `json:"account"` -} - type ResponseGetStorage struct { Key []byte `json:"key"` Value []byte `json:"value"` @@ -50,14 +42,6 @@ type ResponseGetBlock struct { Block *types.Block `json:"block"` } -type ResponseBroadcastTx struct { - Receipt Receipt `json:"receipt"` -} - -type ResponseListUnconfirmedTxs struct { - Txs []types.Tx `json:"txs"` -} - type Receipt struct { TxHash []byte `json:"tx_hash"` CreatesContract uint8 `json:"creates_contract"` @@ -86,10 +70,6 @@ type Peer struct { IsOutbound bool `json:"is_outbound"` } -type ResponseSignTx struct { - Tx types.Tx `json:"tx"` -} - type ResponseListValidators struct { BlockHeight uint `json:"block_height"` BondedValidators []*sm.Validator `json:"bonded_validators"` diff --git a/rpc/core_client/client_methods.go b/rpc/core_client/client_methods.go index d560c68fd..9ac669842 100644 --- a/rpc/core_client/client_methods.go +++ b/rpc/core_client/client_methods.go @@ -5,6 +5,7 @@ package core_client import ( "fmt" "github.com/tendermint/tendermint/account" + acm "github.com/tendermint/tendermint/account" "github.com/tendermint/tendermint/binary" ctypes "github.com/tendermint/tendermint/rpc/core/types" rpctypes "github.com/tendermint/tendermint/rpc/types" @@ -15,23 +16,23 @@ import ( type Client interface { BlockchainInfo(minHeight uint, maxHeight uint) (*ctypes.ResponseBlockchainInfo, error) - BroadcastTx(tx types.Tx) (*ctypes.ResponseBroadcastTx, error) + BroadcastTx(tx types.Tx) (*ctypes.Receipt, error) Call(address []byte, data []byte) (*ctypes.ResponseCall, error) CallCode(code []byte, data []byte) (*ctypes.ResponseCall, error) DumpConsensusState() (*ctypes.ResponseDumpConsensusState, error) DumpStorage(address []byte) (*ctypes.ResponseDumpStorage, error) - GenPrivAccount() (*ctypes.ResponseGenPrivAccount, error) + GenPrivAccount() (*acm.PrivAccount, error) Genesis() (*string, error) - GetAccount(address []byte) (*ctypes.ResponseGetAccount, error) + GetAccount(address []byte) (*acm.Account, error) GetBlock(height uint) (*ctypes.ResponseGetBlock, error) GetName(name string) (*types.NameRegEntry, error) GetStorage(address []byte, key []byte) (*ctypes.ResponseGetStorage, error) ListAccounts() (*ctypes.ResponseListAccounts, error) ListNames() (*ctypes.ResponseListNames, error) - ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) + ListUnconfirmedTxs() ([]types.Tx, error) ListValidators() (*ctypes.ResponseListValidators, error) NetInfo() (*ctypes.ResponseNetInfo, error) - SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseSignTx, error) + SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error) Status() (*ctypes.ResponseStatus, error) } @@ -65,7 +66,7 @@ func (c *ClientHTTP) BlockchainInfo(minHeight uint, maxHeight uint) (*ctypes.Res return response.Result, nil } -func (c *ClientHTTP) BroadcastTx(tx types.Tx) (*ctypes.ResponseBroadcastTx, error) { +func (c *ClientHTTP) BroadcastTx(tx types.Tx) (*ctypes.Receipt, error) { values, err := argsToURLValues([]string{"tx"}, tx) if err != nil { return nil, err @@ -80,10 +81,10 @@ func (c *ClientHTTP) BroadcastTx(tx types.Tx) (*ctypes.ResponseBroadcastTx, erro return nil, err } var response struct { - Result *ctypes.ResponseBroadcastTx `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *ctypes.Receipt `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -215,7 +216,7 @@ func (c *ClientHTTP) DumpStorage(address []byte) (*ctypes.ResponseDumpStorage, e return response.Result, nil } -func (c *ClientHTTP) GenPrivAccount() (*ctypes.ResponseGenPrivAccount, error) { +func (c *ClientHTTP) GenPrivAccount() (*acm.PrivAccount, error) { values, err := argsToURLValues(nil) if err != nil { return nil, err @@ -230,10 +231,10 @@ func (c *ClientHTTP) GenPrivAccount() (*ctypes.ResponseGenPrivAccount, error) { return nil, err } var response struct { - Result *ctypes.ResponseGenPrivAccount `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *acm.PrivAccount `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -275,7 +276,7 @@ func (c *ClientHTTP) Genesis() (*string, error) { return response.Result, nil } -func (c *ClientHTTP) GetAccount(address []byte) (*ctypes.ResponseGetAccount, error) { +func (c *ClientHTTP) GetAccount(address []byte) (*acm.Account, error) { values, err := argsToURLValues([]string{"address"}, address) if err != nil { return nil, err @@ -290,10 +291,10 @@ func (c *ClientHTTP) GetAccount(address []byte) (*ctypes.ResponseGetAccount, err return nil, err } var response struct { - Result *ctypes.ResponseGetAccount `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *acm.Account `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -455,7 +456,7 @@ func (c *ClientHTTP) ListNames() (*ctypes.ResponseListNames, error) { return response.Result, nil } -func (c *ClientHTTP) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) { +func (c *ClientHTTP) ListUnconfirmedTxs() ([]types.Tx, error) { values, err := argsToURLValues(nil) if err != nil { return nil, err @@ -470,10 +471,10 @@ func (c *ClientHTTP) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, e return nil, err } var response struct { - Result *ctypes.ResponseListUnconfirmedTxs `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result []types.Tx `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -545,7 +546,7 @@ func (c *ClientHTTP) NetInfo() (*ctypes.ResponseNetInfo, error) { return response.Result, nil } -func (c *ClientHTTP) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseSignTx, error) { +func (c *ClientHTTP) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error) { values, err := argsToURLValues([]string{"tx", "privAccounts"}, tx, privAccounts) if err != nil { return nil, err @@ -560,10 +561,10 @@ func (c *ClientHTTP) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (* return nil, err } var response struct { - Result *ctypes.ResponseSignTx `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result types.Tx `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -632,7 +633,7 @@ func (c *ClientJSON) BlockchainInfo(minHeight uint, maxHeight uint) (*ctypes.Res return response.Result, nil } -func (c *ClientJSON) BroadcastTx(tx types.Tx) (*ctypes.ResponseBroadcastTx, error) { +func (c *ClientJSON) BroadcastTx(tx types.Tx) (*ctypes.Receipt, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["BroadcastTx"], @@ -644,10 +645,10 @@ func (c *ClientJSON) BroadcastTx(tx types.Tx) (*ctypes.ResponseBroadcastTx, erro return nil, err } var response struct { - Result *ctypes.ResponseBroadcastTx `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *ctypes.Receipt `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -767,7 +768,7 @@ func (c *ClientJSON) DumpStorage(address []byte) (*ctypes.ResponseDumpStorage, e return response.Result, nil } -func (c *ClientJSON) GenPrivAccount() (*ctypes.ResponseGenPrivAccount, error) { +func (c *ClientJSON) GenPrivAccount() (*acm.PrivAccount, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["GenPrivAccount"], @@ -779,10 +780,10 @@ func (c *ClientJSON) GenPrivAccount() (*ctypes.ResponseGenPrivAccount, error) { return nil, err } var response struct { - Result *ctypes.ResponseGenPrivAccount `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *acm.PrivAccount `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -821,7 +822,7 @@ func (c *ClientJSON) Genesis() (*string, error) { return response.Result, nil } -func (c *ClientJSON) GetAccount(address []byte) (*ctypes.ResponseGetAccount, error) { +func (c *ClientJSON) GetAccount(address []byte) (*acm.Account, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["GetAccount"], @@ -833,10 +834,10 @@ func (c *ClientJSON) GetAccount(address []byte) (*ctypes.ResponseGetAccount, err return nil, err } var response struct { - Result *ctypes.ResponseGetAccount `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result *acm.Account `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -983,7 +984,7 @@ func (c *ClientJSON) ListNames() (*ctypes.ResponseListNames, error) { return response.Result, nil } -func (c *ClientJSON) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error) { +func (c *ClientJSON) ListUnconfirmedTxs() ([]types.Tx, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["ListUnconfirmedTxs"], @@ -995,10 +996,10 @@ func (c *ClientJSON) ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, e return nil, err } var response struct { - Result *ctypes.ResponseListUnconfirmedTxs `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result []types.Tx `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { @@ -1064,7 +1065,7 @@ func (c *ClientJSON) NetInfo() (*ctypes.ResponseNetInfo, error) { return response.Result, nil } -func (c *ClientJSON) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (*ctypes.ResponseSignTx, error) { +func (c *ClientJSON) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (types.Tx, error) { request := rpctypes.RPCRequest{ JSONRPC: "2.0", Method: reverseFuncMap["SignTx"], @@ -1076,10 +1077,10 @@ func (c *ClientJSON) SignTx(tx types.Tx, privAccounts []*account.PrivAccount) (* return nil, err } var response struct { - Result *ctypes.ResponseSignTx `json:"result"` - Error string `json:"error"` - Id string `json:"id"` - JSONRPC string `json:"jsonrpc"` + Result types.Tx `json:"result"` + Error string `json:"error"` + Id string `json:"id"` + JSONRPC string `json:"jsonrpc"` } binary.ReadJSON(&response, body, &err) if err != nil { diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 370c44009..56e338820 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -129,10 +129,10 @@ func getNonce(t *testing.T, typ string, addr []byte) uint { if err != nil { t.Fatal(err) } - if ac.Account == nil { + if ac == nil { return 0 } - return ac.Account.Sequence + return ac.Sequence } // get the account @@ -142,28 +142,28 @@ func getAccount(t *testing.T, typ string, addr []byte) *account.Account { if err != nil { t.Fatal(err) } - return ac.Account + return ac } // sign transaction func signTx(t *testing.T, typ string, tx types.Tx, privAcc *account.PrivAccount) types.Tx { client := clients[typ] - resp, err := client.SignTx(tx, []*account.PrivAccount{privAcc}) + signedTx, err := client.SignTx(tx, []*account.PrivAccount{privAcc}) if err != nil { t.Fatal(err) } - return resp.Tx + return signedTx } // broadcast transaction -func broadcastTx(t *testing.T, typ string, tx types.Tx) ctypes.Receipt { +func broadcastTx(t *testing.T, typ string, tx types.Tx) *ctypes.Receipt { client := clients[typ] - resp, err := client.BroadcastTx(tx) + rec, err := client.BroadcastTx(tx) if err != nil { t.Fatal(err) } mempoolCount += 1 - return resp.Receipt + return rec } // dump all storage for an account. currently unused diff --git a/rpc/test/tests.go b/rpc/test/tests.go index 35aafefb7..b98914cb8 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -25,11 +25,11 @@ func testStatus(t *testing.T, typ string) { func testGenPriv(t *testing.T, typ string) { client := clients[typ] - resp, err := client.GenPrivAccount() + privAcc, err := client.GenPrivAccount() if err != nil { t.Fatal(err) } - if len(resp.PrivAccount.Address) == 0 { + if len(privAcc.Address) == 0 { t.Fatal("Failed to generate an address") } } From c8103f6415331bfcf81ba053aae4c8f746906b72 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Mon, 1 Jun 2015 14:59:10 -0700 Subject: [PATCH 13/13] sign-bytes field ordering & escaping of strings --- types/tx.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/types/tx.go b/types/tx.go index 811ab1fd0..19274e955 100644 --- a/types/tx.go +++ b/types/tx.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "errors" "io" @@ -135,7 +136,7 @@ type SendTx struct { } func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err) for i, in := range tx.Inputs { in.WriteSignBytes(w, n, err) @@ -168,7 +169,7 @@ type CallTx struct { } func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err) binary.WriteTo([]byte(Fmt(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err) tx.Input.WriteSignBytes(w, n, err) @@ -189,10 +190,11 @@ type NameTx struct { } func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) - binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"name":"%s","data":"%s"`, TxTypeName, tx.Name, tx.Data)), w, n, err) - binary.WriteTo([]byte(Fmt(`,"fee":%v,"input":`, tx.Fee)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"data":%s,"fee":%v`, TxTypeName, jsonEscape(tx.Data), tx.Fee)), w, n, err) + binary.WriteTo([]byte(Fmt(`,"input":`, tx.Input)), w, n, err) tx.Input.WriteSignBytes(w, n, err) + binary.WriteTo([]byte(Fmt(`,"name":%s`, jsonEscape(tx.Name))), w, n, err) binary.WriteTo([]byte(`}]}`), w, n, err) } @@ -236,7 +238,7 @@ type BondTx struct { } func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err) for i, in := range tx.Inputs { in.WriteSignBytes(w, n, err) @@ -269,7 +271,7 @@ type UnbondTx struct { } func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err) } @@ -286,7 +288,7 @@ type RebondTx struct { } func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { - binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) + binary.WriteTo([]byte(Fmt(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err) } @@ -316,3 +318,14 @@ func TxId(chainID string, tx Tx) []byte { signBytes := account.SignBytes(chainID, tx) return binary.BinaryRipemd160(signBytes) } + +//-------------------------------------------------------------------------------- + +// Contract: This function is deterministic and completely reversible. +func jsonEscape(str string) string { + escapedBytes, err := json.Marshal(str) + if err != nil { + panic(Fmt("Error json-escaping a string", str)) + } + return string(escapedBytes) +}