diff --git a/app/app.go b/app/app.go index 94d37c43db..2107dc17e6 100644 --- a/app/app.go +++ b/app/app.go @@ -1,6 +1,7 @@ package app import ( + "encoding/hex" "encoding/json" "strings" @@ -77,13 +78,18 @@ func (app *Basecoin) SetOption(key string, value string) string { app.state.SetChainID(value) return "Success" case "account": - var acc types.Account + var acc GenesisAccount err := json.Unmarshal([]byte(value), &acc) if err != nil { return "Error decoding acc message: " + err.Error() } - app.state.SetAccount(acc.PubKey.Address(), &acc) - app.logger.Info("SetAccount", "addr", acc.PubKey.Address(), "acc", acc) + acc.Balance.Sort() + addr, err := acc.GetAddr() + if err != nil { + return "Invalid address: " + err.Error() + } + app.state.SetAccount(addr, acc.ToAccount()) + app.logger.Info("SetAccount", "addr", hex.EncodeToString(addr), "acc", acc) return "Success" } diff --git a/app/app_test.go b/app/app_test.go index c56708e2b3..d37a6ade8e 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -1,14 +1,15 @@ package app import ( + "encoding/hex" "encoding/json" - "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/state" "github.com/tendermint/basecoin/types" wire "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" @@ -103,6 +104,7 @@ func TestSplitKey(t *testing.T) { func TestSetOption(t *testing.T) { assert := assert.New(t) + require := require.New(t) eyesCli := eyes.NewLocalClient("", 0) app := NewBasecoin(eyesCli) @@ -114,11 +116,43 @@ func TestSetOption(t *testing.T) { assert.EqualValues(app.GetState().GetChainID(), chainID) assert.EqualValues(res, "Success") + // make a nice account... accIn := types.MakeAcc("input0") accsInBytes, err := json.Marshal(accIn.Account) assert.Nil(err) res = app.SetOption("base/account", string(accsInBytes)) - assert.EqualValues(res, "Success") + require.EqualValues(res, "Success") + // make sure it is set correctly, with some balance + acct := state.GetAccount(app.GetState(), accIn.PubKey.Address()) + require.NotNil(acct) + assert.Equal(accIn.Balance, acct.Balance) + + // let's parse an account with badly sorted coins... + unsortAddr, err := hex.DecodeString("C471FB670E44D219EE6DF2FC284BE38793ACBCE1") + require.Nil(err) + unsortCoins := types.Coins{{"BTC", 789}, {"eth", 123}} + unsortAcc := `{ + "pub_key": { + "type": "ed25519", + "data": "AD084F0572C116D618B36F2EB08240D1BAB4B51716CCE0E7734B89C8936DCE9A" + }, + "coins": [ + { + "denom": "eth", + "amount": 123 + }, + { + "denom": "BTC", + "amount": 789 + } + ] +}` + res = app.SetOption("base/account", unsortAcc) + require.EqualValues(res, "Success") + acct = state.GetAccount(app.GetState(), unsortAddr) + require.NotNil(acct) + assert.True(acct.Balance.IsValid()) + assert.Equal(unsortCoins, acct.Balance) res = app.SetOption("base/dslfkgjdas", "") assert.NotEqual(res, "Success") @@ -128,6 +162,7 @@ func TestSetOption(t *testing.T) { res = app.SetOption("dslfkgjdas/szfdjzs", "") assert.NotEqual(res, "Success") + } // Test CheckTx and DeliverTx with insufficient and sufficient balance @@ -179,7 +214,5 @@ func TestQuery(t *testing.T) { Path: "/account", Data: at.accIn.Account.PubKey.Address(), }) - fmt.Println(resQueryPreCommit) - fmt.Println(resQueryPostCommit) assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit") } diff --git a/app/genesis.go b/app/genesis.go index f1c36914f8..fa6a5ac305 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -1,11 +1,14 @@ package app import ( + "bytes" "encoding/json" "github.com/pkg/errors" "github.com/tendermint/basecoin/types" + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire/data" cmn "github.com/tendermint/tmlibs/common" ) @@ -49,7 +52,7 @@ type FullGenesisDoc struct { } type GenesisDoc struct { - Accounts []types.Account `json:"accounts"` + Accounts []GenesisAccount `json:"accounts"` PluginOptions []json.RawMessage `json:"plugin_options"` pluginOptions []keyValue // unmarshaled rawmessages @@ -98,3 +101,40 @@ func parseGenesisList(kvz_ []json.RawMessage) (kvz []keyValue, err error) { } return kvz, nil } + +/**** code to parse accounts from genesis docs ***/ + +type GenesisAccount struct { + Address data.Bytes `json:"address"` + // this from types.Account (don't know how to embed this properly) + PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known. + Sequence int `json:"sequence"` + Balance types.Coins `json:"coins"` +} + +func (g GenesisAccount) ToAccount() *types.Account { + return &types.Account{ + PubKey: g.PubKey, + Sequence: g.Sequence, + Balance: g.Balance, + } +} + +func (g GenesisAccount) GetAddr() ([]byte, error) { + noAddr, noPk := len(g.Address) == 0, g.PubKey.Empty() + + if noAddr { + if noPk { + return nil, errors.New("No address given") + } + return g.PubKey.Address(), nil + } + if noPk { // but is addr... + return g.Address, nil + } + // now, we have both, make sure they check out + if bytes.Equal(g.Address, g.PubKey.Address()) { + return g.Address, nil + } + return nil, errors.New("Address and pubkey don't match") +} diff --git a/app/genesis_test.go b/app/genesis_test.go index df47827465..b2a590bcf3 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -7,12 +7,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/basecoin/types" "github.com/tendermint/go-crypto" eyescli "github.com/tendermint/merkleeyes/client" + cmn "github.com/tendermint/tmlibs/common" ) const genesisFilepath = "./testdata/genesis.json" +const genesisAcctFilepath = "./testdata/genesis2.json" func TestLoadGenesis(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -34,8 +36,12 @@ func TestLoadGenesis(t *testing.T) { // make sure balance is proper assert.Equal(2, len(acct.Balance)) - assert.EqualValues(12345, acct.Balance[0].Amount) - assert.EqualValues("blank", acct.Balance[0].Denom) + assert.True(acct.Balance.IsValid()) + // note, that we now sort them to be valid + assert.EqualValues(654321, acct.Balance[0].Amount) + assert.EqualValues("ETH", acct.Balance[0].Denom) + assert.EqualValues(12345, acct.Balance[1].Amount) + assert.EqualValues("blank", acct.Balance[1].Denom) // and public key is parsed properly apk := acct.PubKey.Unwrap() @@ -46,6 +52,51 @@ func TestLoadGenesis(t *testing.T) { } } +// Fix for issue #89, change the parse format for accounts in genesis.json +func TestLoadGenesisAccountAddress(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + eyesCli := eyescli.NewLocalClient("", 0) + app := NewBasecoin(eyesCli) + err := app.LoadGenesis(genesisAcctFilepath) + require.Nil(err, "%+v", err) + + // check the chain id + assert.Equal("addr_accounts_chain", app.GetState().GetChainID()) + + // make sure the accounts were set properly + cases := []struct { + addr string + exists bool + hasPubkey bool + coins types.Coins + }{ + // this comes from a public key, should be stored proper (alice) + {"62035D628DE7543332544AA60D90D3693B6AD51B", true, true, types.Coins{{"one", 111}}}, + // this comes from an address, should be stored proper (bob) + {"C471FB670E44D219EE6DF2FC284BE38793ACBCE1", true, false, types.Coins{{"two", 222}}}, + // this one had a mismatched address and pubkey, should not store under either (carl) + {"1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is given addr + {"700BEC5ED18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is addr of the given pubkey + // this comes from a secp256k1 public key, should be stored proper (sam) + {"979F080B1DD046C452C2A8A250D18646C6B669D4", true, true, types.Coins{{"four", 444}}}, + } + + for _, tc := range cases { + addr, err := hex.DecodeString(tc.addr) + require.Nil(err, tc.addr) + acct := app.GetState().GetAccount(addr) + if !tc.exists { + assert.Nil(acct, tc.addr) + } else if assert.NotNil(acct, tc.addr) { + // it should and does exist... + assert.True(acct.Balance.IsValid()) + assert.Equal(tc.coins, acct.Balance) + assert.Equal(!tc.hasPubkey, acct.PubKey.Empty(), tc.addr) + } + } +} + func TestParseGenesisList(t *testing.T) { assert, require := assert.New(t), require.New(t) diff --git a/app/testdata/genesis2.json b/app/testdata/genesis2.json new file mode 100644 index 0000000000..a880b3c64c --- /dev/null +++ b/app/testdata/genesis2.json @@ -0,0 +1,52 @@ +{ + "chain_id": "addr_accounts_chain", + "app_options": { + "accounts": [{ + "name": "alice", + "pub_key": { + "type": "ed25519", + "data": "DBD9A46C45868F0A37C92B53113C09B048FBD87B5FBC2F8B199052973B8FAA36" + }, + "coins": [ + { + "denom": "one", + "amount": 111 + } + ] + }, { + "name": "bob", + "address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1", + "coins": [ + { + "denom": "two", + "amount": 222 + } + ] + }, { + "name": "carl", + "address": "1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9", + "pub_key": { + "type": "ed25519", + "data": "177C0AC45E86257F0708DC085D592AB22AAEECD1D26381B757F7C96135921858" + }, + "coins": [ + { + "denom": "three", + "amount": 333 + } + ] + }, { + "name": "sam", + "pub_key": { + "type": "secp256k1", + "data": "02AA8342F63CCCCE6DDB128525BA048CE0B2993DA3B4308746E1F216361A87651E" + }, + "coins": [ + { + "denom": "four", + "amount": 444 + } + ] + }] + } +} diff --git a/cmd/basecli/commands/adapters.go b/cmd/basecli/commands/adapters.go index f475456741..a1899019c1 100644 --- a/cmd/basecli/commands/adapters.go +++ b/cmd/basecli/commands/adapters.go @@ -107,9 +107,15 @@ func (t SendTxReader) ReadTxFlags(flags interface{}, pk crypto.PubKey) (interfac return nil, err } + // get addr if available + var addr []byte + if !pk.Empty() { + addr = pk.Address() + } + // craft the tx input := btypes.TxInput{ - Address: pk.Address(), + Address: addr, Coins: amountCoins, Sequence: data.Sequence, } @@ -174,9 +180,15 @@ func (t AppTxReader) ReadTxFlags(data *AppFlags, app string, appData []byte, pk return nil, err } + // get addr if available + var addr []byte + if !pk.Empty() { + addr = pk.Address() + } + // craft the tx input := btypes.TxInput{ - Address: pk.Address(), + Address: addr, Coins: amountCoins, Sequence: data.Sequence, } diff --git a/cmd/commands/reset.go b/cmd/commands/reset.go index 296e58ad12..4d38f94bfe 100644 --- a/cmd/commands/reset.go +++ b/cmd/commands/reset.go @@ -1,13 +1,9 @@ package commands import ( - "os" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/tendermint/tendermint/config" - "github.com/tendermint/tmlibs/cli" + tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" ) var UnsafeResetAllCmd = &cobra.Command{ @@ -17,9 +13,10 @@ var UnsafeResetAllCmd = &cobra.Command{ } func unsafeResetAllCmd(cmd *cobra.Command, args []string) error { - rootDir := viper.GetString(cli.HomeFlag) - // wipe out rootdir if it exists before recreating it - os.RemoveAll(rootDir) - config.EnsureRoot(rootDir) + cfg, err := getTendermintConfig() + if err != nil { + return err + } + tmcmd.ResetAll(cfg.DBDir(), cfg.PrivValidatorFile(), logger) return nil } diff --git a/cmd/commands/start.go b/cmd/commands/start.go index 053e6e400f..7a15f59df5 100644 --- a/cmd/commands/start.go +++ b/cmd/commands/start.go @@ -122,14 +122,22 @@ func startBasecoinABCI(basecoinApp *app.Basecoin) error { return nil } -func startTendermint(dir string, basecoinApp *app.Basecoin) error { +func getTendermintConfig() (*config.Config, error) { cfg := config.DefaultConfig() err := viper.Unmarshal(cfg) if err != nil { - return err + return nil, err } cfg.SetRoot(cfg.RootDir) config.EnsureRoot(cfg.RootDir) + return cfg, nil +} + +func startTendermint(dir string, basecoinApp *app.Basecoin) error { + cfg, err := getTendermintConfig() + if err != nil { + return err + } // TODO: parse the log level from the config properly (multi modules) // but some tm code must be refactored for better usability diff --git a/types/coin.go b/types/coin.go index e34f7d1bdb..0499e58c0f 100644 --- a/types/coin.go +++ b/types/coin.go @@ -3,8 +3,11 @@ package types import ( "fmt" "regexp" + "sort" "strconv" "strings" + + "github.com/pkg/errors" ) type Coin struct { @@ -53,9 +56,8 @@ func (coins Coins) String() string { } func ParseCoins(str string) (Coins, error) { - split := strings.Split(str, ",") - var coins []Coin + var coins Coins for _, el := range split { if len(el) > 0 { @@ -67,6 +69,12 @@ func ParseCoins(str string) (Coins, error) { } } + // ensure they are in proper order, to avoid random failures later + coins.Sort() + if !coins.IsValid() { + return nil, errors.Errorf("ParseCoins invalid: %#v", coins) + } + return coins, nil } @@ -195,3 +203,10 @@ func (coins Coins) IsNonnegative() bool { } return true } + +/*** Implement Sort interface ***/ + +func (c Coins) Len() int { return len(c) } +func (c Coins) Less(i, j int) bool { return c[i].Denom < c[j].Denom } +func (c Coins) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c Coins) Sort() { sort.Sort(c) } diff --git a/types/coin_test.go b/types/coin_test.go index 8cbc708a49..bf99d0caab 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestCoins(t *testing.T) { @@ -56,30 +55,79 @@ func TestCoins(t *testing.T) { //Test the parse coin and parse coins functionality func TestParse(t *testing.T) { - assert, require := assert.New(t), require.New(t) + assert := assert.New(t) - makeCoin := func(str string) Coin { - coin, err := ParseCoin(str) - require.Nil(err) - return coin + cases := []struct { + input string + valid bool // if false, we expect an error on parse + expected Coins // if valid is true, make sure this is returned + }{ + {"", true, nil}, + {"1foo", true, Coins{{"foo", 1}}}, + {"10bar", true, Coins{{"bar", 10}}}, + {"99bar,1foo", true, Coins{{"bar", 99}, {"foo", 1}}}, + {"98 bar , 1 foo ", true, Coins{{"bar", 98}, {"foo", 1}}}, + {"2foo, 97 bar", true, Coins{{"bar", 97}, {"foo", 2}}}, } - makeCoins := func(str string) Coins { - coin, err := ParseCoins(str) - require.Nil(err) - return coin + for _, tc := range cases { + res, err := ParseCoins(tc.input) + if !tc.valid { + assert.NotNil(err, tc.input) + } else if assert.Nil(err, "%s: %+v", tc.input, err) { + assert.Equal(tc.expected, res) + } } - //testing ParseCoin Function - assert.Equal(Coin{}, makeCoin(""), "ParseCoin makes bad empty coin") - assert.Equal(Coin{"fooCoin", 1}, makeCoin("1fooCoin"), "ParseCoin makes bad coins") - assert.Equal(Coin{"barCoin", 10}, makeCoin("10 barCoin"), "ParseCoin makes bad coins") - - //testing ParseCoins Function - assert.True(Coins{{"fooCoin", 1}}.IsEqual(makeCoins("1fooCoin")), - "ParseCoins doesn't parse a single coin") - assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99barCoin,1fooCoin")), - "ParseCoins doesn't properly parse two coins") - assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99 barCoin, 1 fooCoin")), - "ParseCoins doesn't properly parse two coins which use spaces") +} + +func TestSortCoins(t *testing.T) { + assert := assert.New(t) + + good := Coins{ + Coin{"GAS", 1}, + Coin{"MINERAL", 1}, + Coin{"TREE", 1}, + } + empty := Coins{ + Coin{"GOLD", 0}, + } + badSort1 := Coins{ + Coin{"TREE", 1}, + Coin{"GAS", 1}, + Coin{"MINERAL", 1}, + } + badSort2 := Coins{ // both are after the first one, but the second and third are in the wrong order + Coin{"GAS", 1}, + Coin{"TREE", 1}, + Coin{"MINERAL", 1}, + } + badAmt := Coins{ + Coin{"GAS", 1}, + Coin{"TREE", 0}, + Coin{"MINERAL", 1}, + } + dup := Coins{ + Coin{"GAS", 1}, + Coin{"GAS", 1}, + Coin{"MINERAL", 1}, + } + + cases := []struct { + coins Coins + before, after bool // valid before/after sort + }{ + {good, true, true}, + {empty, false, false}, + {badSort1, false, true}, + {badSort2, false, true}, + {badAmt, false, false}, + {dup, false, false}, + } + + for _, tc := range cases { + assert.Equal(tc.before, tc.coins.IsValid()) + tc.coins.Sort() + assert.Equal(tc.after, tc.coins.IsValid()) + } }