Browse Source

Merge pull request #83 from tendermint/namereg_rebase

Namereg rebase
pull/87/head
Jae Kwon 9 years ago
parent
commit
713b1f0074
25 changed files with 1065 additions and 213 deletions
  1. +19
    -6
      config/tendermint_test/config.go
  2. +1
    -2
      consensus/types/proposal.go
  3. +1
    -1
      consensus/types/proposal_test.go
  4. +4
    -4
      rpc/core/accounts.go
  5. +4
    -6
      rpc/core/mempool.go
  6. +29
    -0
      rpc/core/names.go
  7. +2
    -0
      rpc/core/routes.go
  8. +2
    -2
      rpc/core/txs.go
  9. +5
    -20
      rpc/core/types/responses.go
  10. +19
    -16
      rpc/core_client/client.go
  11. +172
    -55
      rpc/core_client/client_methods.go
  12. +8
    -0
      rpc/test/client_rpc_test.go
  13. +2
    -2
      rpc/test/client_ws_test.go
  14. +30
    -21
      rpc/test/helpers.go
  15. +83
    -14
      rpc/test/tests.go
  16. +77
    -0
      state/block_cache.go
  17. +115
    -1
      state/execution.go
  18. +6
    -0
      state/genesis.go
  19. +48
    -0
      state/state.go
  20. +268
    -21
      state/state_test.go
  21. +48
    -0
      types/names.go
  22. +70
    -10
      types/tx.go
  23. +5
    -5
      types/tx_test.go
  24. +46
    -25
      types/tx_utils.go
  25. +1
    -2
      types/vote.go

+ 19
- 6
config/tendermint_test/config.go View File

@ -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
}
]


+ 1
- 2
consensus/types/proposal.go View File

@ -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)


+ 1
- 1
consensus/types/proposal_test.go View File

@ -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)


+ 4
- 4
rpc/core/accounts.go View File

@ -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) {


+ 4
- 6
rpc/core/mempool.go View File

@ -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
}

+ 29
- 0
rpc/core/names.go View File

@ -0,0 +1,29 @@
package core
import (
"fmt"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/types"
)
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 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
}

+ 2
- 0
rpc/core/routes.go View File

@ -20,6 +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{}),
"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"}),
}

+ 2
- 2
rpc/core/txs.go View File

@ -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
}

+ 5
- 20
rpc/core/types/responses.go View File

@ -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"`
@ -100,3 +80,8 @@ type ResponseDumpConsensusState struct {
RoundState string `json:"round_state"`
PeerRoundStates []string `json:"peer_round_states"`
}
type ResponseListNames struct {
BlockHeight uint `json:"block_height"`
Names []*types.NameRegEntry `json:"names"`
}

+ 19
- 16
rpc/core_client/client.go View File

@ -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"
@ -18,20 +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",
"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",
}
/*


+ 172
- 55
rpc/core_client/client_methods.go View File

@ -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,21 +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)
ListUnconfirmedTxs() (*ctypes.ResponseListUnconfirmedTxs, error)
ListNames() (*ctypes.ResponseListNames, 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)
}
@ -63,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
@ -78,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 {
@ -213,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
@ -228,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 {
@ -273,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
@ -288,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 {
@ -333,6 +336,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 {
@ -393,7 +426,37 @@ 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["ListNames"], 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.ResponseListNames `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) ListUnconfirmedTxs() ([]types.Tx, error) {
values, err := argsToURLValues(nil)
if err != nil {
return nil, err
@ -408,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 {
@ -483,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
@ -498,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 {
@ -570,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"],
@ -582,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 {
@ -705,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"],
@ -717,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 {
@ -759,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"],
@ -771,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 {
@ -813,6 +876,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",
@ -867,7 +957,34 @@ 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["ListNames"],
Params: []interface{}{},
Id: 0,
}
body, err := c.RequestResponse(request)
if err != nil {
return nil, err
}
var response struct {
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 {
return nil, err
}
if response.Error != "" {
return nil, fmt.Errorf(response.Error)
}
return response.Result, nil
}
func (c *ClientJSON) ListUnconfirmedTxs() ([]types.Tx, error) {
request := rpctypes.RPCRequest{
JSONRPC: "2.0",
Method: reverseFuncMap["ListUnconfirmedTxs"],
@ -879,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 {
@ -948,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"],
@ -960,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 {


+ 8
- 0
rpc/test/client_rpc_test.go View File

@ -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")
}

+ 2
- 2
rpc/test/client_ws_test.go View File

@ -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)


+ 30
- 21
rpc/test/helpers.go View File

@ -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
}
@ -102,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
}
@ -115,7 +107,14 @@ 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
}
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(chainID, user[0])
return tx
}
@ -124,16 +123,16 @@ func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim,
// 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 {
t.Fatal(err)
}
if ac.Account == nil {
if ac == nil {
return 0
}
return uint64(ac.Account.Sequence)
return ac.Sequence
}
// get the account
@ -143,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
@ -210,6 +209,16 @@ 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]
entry, err := client.GetName(name)
if err != nil {
t.Fatal(err)
}
return entry
}
//--------------------------------------------------------------------------------
// utility verification function


+ 83
- 14
rpc/test/tests.go View File

@ -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()
@ -23,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")
}
}
@ -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
@ -190,3 +186,76 @@ func testCall(t *testing.T, typ string) {
expected := []byte{0xb}
callContract(t, client, contractAddr, data, expected)
}
func testNameReg(t *testing.T, typ string) {
client := clients[typ]
con := newWSCon(t)
eid := types.EventStringNewBlock()
subscribe(t, con, eid)
defer func() {
unsubscribe(t, con, eid)
con.Close()
}()
types.MinNameRegistrationPeriod = 1
// register a new name, check if its there
// since entries ought to be unique and these run against different clients, we append the typ
name := "ye_old_domain_name_" + typ
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("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
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("Err on entry.Data: 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+1)
tx.Sign(chainID, user[1])
_, err := client.BroadcastTx(tx)
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))
}
}

+ 77
- 0
state/block_cache.go View File

@ -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 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[name] = nameInfo{entry, false, false}
return entry
}
}
func (cache *BlockCache) UpdateNameRegEntry(entry *types.NameRegEntry) {
name := entry.Name
// SANITY CHECK
_, removed, _ := cache.names[name].unpack()
if removed {
panic("UpdateNameRegEntry on a removed name")
}
// SANITY CHECK END
cache.names[name] = nameInfo{entry, false, true}
}
func (cache *BlockCache) RemoveNameRegEntry(name string) {
// SANITY CHECK
_, removed, _ := cache.names[name].unpack()
if removed {
panic("RemoveNameRegEntry on a removed entry")
}
// SANITY CHECK END
cache.names[name] = nameInfo{nil, true, false}
}
// BlockCache.names
//-------------------------------------
// CONTRACT the updates are in deterministic order.
func (cache *BlockCache) Sync() {
@ -202,6 +243,32 @@ func (cache *BlockCache) Sync() {
}
}
// Determine order for names
// note names may be of any length less than some limit
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(nameStr)
if !removed {
panic(Fmt("Could not remove namereg entry to be removed: %s", nameStr))
}
} else {
if entry == nil {
continue
}
if dirty {
cache.backend.UpdateNameRegEntry(entry)
}
}
}
}
//-----------------------------------------------------------------------------
@ -225,3 +292,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
}

+ 115
- 1
state/execution.go View File

@ -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,120 @@ 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(_s.ChainID, 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
}
// validate the input strings
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 := 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)
if entry != nil {
var expired bool
// if the entry already exists, and hasn't expired, we must be owner
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))
return types.ErrIncorrectOwner
}
} 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)
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"
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 < 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{
Name: tx.Name,
Owner: tx.Input.Address,
Data: tx.Data,
Expires: lastBlockHeight + expiresIn,
}
log.Debug("Creating namereg entry", "name", entry.Name, "expiresIn", 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 {


+ 6
- 0
state/genesis.go View File

@ -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,
}
}

+ 48
- 0
state/state.go View File

@ -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,45 @@ func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) {
// State.storage
//-------------------------------------
// State.nameReg
func (s *State) GetNameRegEntry(name string) *types.NameRegEntry {
_, value := s.nameReg.Get(name)
if value == nil {
return nil
}
entry := value.(*types.NameRegEntry)
return entry.Copy()
}
func (s *State) UpdateNameRegEntry(entry *types.NameRegEntry) bool {
return s.nameReg.Set(entry.Name, entry)
}
func (s *State) RemoveNameRegEntry(name string) 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) {


+ 268
- 21
state/state_test.go View File

@ -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,31 +175,13 @@ 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++ {
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)
@ -220,6 +211,168 @@ 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(state.ChainID, 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(state.ChainID, 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(state.ChainID, 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(state.ChainID, 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(state.ChainID, 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(state.ChainID, 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(state.ChainID, 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(state.ChainID, 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(state.ChainID, 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)
// test removal
amt = fee
data = ""
tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee)
tx.Sign(state.ChainID, 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(state.ChainID, 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(state.ChainID, 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.
// TODO: test for unbonding validators.
func TestTxs(t *testing.T) {
@ -268,6 +421,100 @@ func TestTxs(t *testing.T) {
}
}
// CallTx. Just runs through it and checks the transfer. See vm, rpc tests for more
{
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(state.ChainID, 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 := "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
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(state.ChainID, 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 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(state.ChainID, tx)
err = execTxWithState(state, tx, true)
if err != types.ErrTxInvalidString {
t.Errorf("Expected invalid string error. Got: %s", err.Error())
}
}
// BondTx.
{
state := state.Copy()


+ 48
- 0
types/names.go View File

@ -0,0 +1,48 @@
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
Data string `json:"data"` // data to store under this name
Expires uint64 `json:"expires"` // block at which this entry expires
}
func (entry *NameRegEntry) Copy() *NameRegEntry {
entryCopy := *entry
return &entryCopy
}

+ 70
- 10
types/tx.go View File

@ -1,6 +1,7 @@
package types
import (
"encoding/json"
"errors"
"io"
@ -18,6 +19,8 @@ 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")
ErrIncorrectOwner = errors.New("Error incorrect owner")
)
type ErrTxInvalidSequence struct {
@ -35,6 +38,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 +54,7 @@ const (
// Account transactions
TxTypeSend = byte(0x01)
TxTypeCall = byte(0x02)
TxTypeName = byte(0x03)
// Validation transactions
TxTypeBond = byte(0x11)
@ -63,6 +68,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},
@ -130,8 +136,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`, 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)
@ -164,8 +169,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`, 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)
@ -178,6 +182,54 @@ func (tx *CallTx) String() string {
//-----------------------------------------------------------------------------
type NameTx struct {
Input *TxInput `json:"input"`
Name string `json:"name"`
Data string `json:"data"`
Fee uint64 `json:"fee"`
}
func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) {
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)
}
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))
}
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) BaseEntryCost() uint64 {
return BaseEntryCost(tx.Name, tx.Data)
}
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"`
@ -186,8 +238,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`, 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)
@ -220,8 +271,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`, jsonEscape(chainID))), w, n, err)
binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err)
}
@ -238,8 +288,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`, jsonEscape(chainID))), w, n, err)
binary.WriteTo([]byte(Fmt(`,"tx":[%v,{"address":"%X","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err)
}
@ -269,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)
}

+ 5
- 5
types/tx_test.go View File

@ -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")


+ 46
- 25
types/tx_utils.go View File

@ -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,
}
@ -103,6 +95,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 string, 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 := acc.Sequence + 1
return NewNameTxWithNonce(from, name, data, amt, fee, nonce), nil
}
func NewNameTxWithNonce(from account.PubKey, name, data string, amt, fee uint64, nonce uint) *NameTx {
addr := from.Address()
input := &TxInput{
Address: addr,
Amount: amt,
Sequence: nonce,
Signature: account.SignatureEd25519{},
PubKey: from,
}
return &NameTx{
Input: input,
Name: name,
Data: data,
Fee: fee,
}
}
func (tx *NameTx) Sign(chainID string, privAccount *account.PrivAccount) {
tx.Input.PubKey = privAccount.PubKey
tx.Input.Signature = privAccount.Sign(chainID, tx)
}
//----------------------------------------------------------------------------
// BondTx interface for adding inputs/outputs and adding signatures
@ -124,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,
})


+ 1
- 2
types/vote.go View File

@ -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)
}


Loading…
Cancel
Save