diff --git a/app/v0/protocol_v0.go b/app/v0/protocol_v0.go index 544e1d07f..9067e10ee 100644 --- a/app/v0/protocol_v0.go +++ b/app/v0/protocol_v0.go @@ -268,7 +268,9 @@ func (p *ProtocolV0) configRouters() { AddRoute("gov", gov.NewHandler(p.govKeeper)). AddRoute("service", service.NewHandler(p.serviceKeeper)). AddRoute("guardian", guardian.NewHandler(p.guardianKeeper)) + p.queryRouter. + AddRoute("acc", auth.NewQuerier(p.accountMapper)). AddRoute("gov", gov.NewQuerier(p.govKeeper)). AddRoute("stake", stake.NewQuerier(p.StakeKeeper, p.cdc)) } diff --git a/client/bank/lcd/query.go b/client/bank/lcd/query.go index 7b0233cca..16cf18c0d 100644 --- a/client/bank/lcd/query.go +++ b/client/bank/lcd/query.go @@ -1,9 +1,7 @@ package lcd import ( - "fmt" "net/http" - "strings" "github.com/gorilla/mux" @@ -26,6 +24,7 @@ func QueryBalancesRequestHandlerFn( w.Header().Set("Content-Type", "application/json") vars := mux.Vars(r) bech32addr := vars["address"] + cliCtx = cliCtx.WithAccountDecoder(decoder) addr, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { @@ -33,26 +32,17 @@ func QueryBalancesRequestHandlerFn( return } - res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - // the query will return empty if there is no data for this account - if len(res) == 0 { - w.WriteHeader(http.StatusNoContent) + if err := cliCtx.EnsureAccountExistsFromAddr(addr); err != nil { + utils.WriteErrorResponse(w, http.StatusNoContent, err.Error()) return } - // decode the value - account, err := decoder(res) + acc, err := cliCtx.GetAccount(addr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return } - utils.PostProcessResponse(w, cdc, account.GetCoins(), cliCtx.Indent) + utils.PostProcessResponse(w, cdc, acc.GetCoins(), cliCtx.Indent) } } @@ -63,6 +53,7 @@ func QueryAccountRequestHandlerFn(storeName string, cdc *codec.Codec, return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bech32addr := vars["address"] + cliCtx = cliCtx.WithAccountDecoder(decoder) addr, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { @@ -70,26 +61,18 @@ func QueryAccountRequestHandlerFn(storeName string, cdc *codec.Codec, return } - res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't query account. Error: %s", err.Error())) + if err := cliCtx.EnsureAccountExistsFromAddr(addr); err != nil { + utils.WriteErrorResponse(w, http.StatusNoContent, err.Error()) return } - // the query will return empty if there is no data for this account - if len(res) == 0 { - w.WriteHeader(http.StatusNoContent) - return - } - - // decode the value - account, err := decoder(res) + acc, err := cliCtx.GetAccount(addr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - accountRes, err := bank.ConvertAccountCoin(cliCtx, account) + accountRes, err := bank.ConvertAccountCoin(cliCtx, acc) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return diff --git a/client/context/query.go b/client/context/query.go index fcbeb2146..166ca5025 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -67,15 +67,13 @@ func (cliCtx CLIContext) GetAccount(address []byte) (auth.Account, error) { return nil, errors.New("account decoder required but not provided") } - res, err := cliCtx.QueryStore(auth.AddressStoreKey(address), cliCtx.AccountStore) + res, err := cliCtx.queryAccount(address) if err != nil { return nil, err - } else if len(res) == 0 { - return nil, ErrInvalidAccount(address) } - account, err := cliCtx.AccDecoder(res) - if err != nil { + var account auth.Account + if err := cliCtx.Codec.UnmarshalJSON(res, &account); err != nil { return nil, err } @@ -138,16 +136,26 @@ func (cliCtx CLIContext) EnsureAccountExists() error { // address. Instead of using the context's from name, a direct address is // given. An error is returned if it does not. func (cliCtx CLIContext) EnsureAccountExistsFromAddr(addr sdk.AccAddress) error { - accountBytes, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), cliCtx.AccountStore) + _, err := cliCtx.queryAccount(addr) + return err +} + +// queryAccount queries an account using custom query endpoint of auth module +// returns an error if result is `null` otherwise account data +func (cliCtx CLIContext) queryAccount(addr sdk.AccAddress) ([]byte, error) { + bz, err := cliCtx.Codec.MarshalJSON(auth.NewQueryAccountParams(addr)) if err != nil { - return err + return nil, err } - if len(accountBytes) == 0 { - return ErrInvalidAccount(addr) + route := fmt.Sprintf("custom/%s/%s", cliCtx.AccountStore, auth.QueryAccount) + + res, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return nil, err } - return nil + return res, nil } // query performs a query from a Tendermint node with the provided store name @@ -398,7 +406,6 @@ func (cliCtx CLIContext) GetLatestHeight() (int64, error) { return status.SyncInfo.LatestBlockHeight, nil } - func (cliCtx CLIContext) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { client := &http.Client{} url := strings.Replace(cliCtx.NodeURI, "tcp", "http", 1) diff --git a/modules/auth/account_test.go b/modules/auth/account_test.go index c01a756e8..4ec139b04 100644 --- a/modules/auth/account_test.go +++ b/modules/auth/account_test.go @@ -5,20 +5,10 @@ import ( "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" - - codec "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/codec" sdk "github.com/irisnet/irishub/types" ) -func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { - key := ed25519.GenPrivKey() - pub := key.PubKey() - addr := sdk.AccAddress(pub.Address()) - return key, pub, addr -} - func TestBaseAddressPubKey(t *testing.T) { _, pub1, addr1 := keyPubAddr() _, pub2, addr2 := keyPubAddr() diff --git a/modules/auth/ante_test.go b/modules/auth/ante_test.go index 2ef6877a6..4fcbd0d2a 100644 --- a/modules/auth/ante_test.go +++ b/modules/auth/ante_test.go @@ -1,113 +1,20 @@ package auth import ( - "fmt" "testing" + "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/modules/params" sdk "github.com/irisnet/irishub/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/multisig" "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/crypto/multisig" - "github.com/irisnet/irishub/codec" - "github.com/irisnet/irishub/modules/params" ) -func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { - return sdk.NewTestMsg(addrs...) -} - -func newStdFee() StdFee { - return NewStdFee(5000, - sdk.NewInt64Coin("atom", 150), - ) -} - -// coins to more than cover the fee -func newCoins() sdk.Coins { - return sdk.Coins{ - sdk.NewInt64Coin("atom", 10000000), - } -} - -// generate a priv key and return it with its address -func privAndAddr() (crypto.PrivKey, sdk.AccAddress) { - priv := ed25519.GenPrivKey() - addr := sdk.AccAddress(priv.PubKey().Address()) - return priv, addr -} - -// run the tx through the anteHandler and ensure its valid -func checkValidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, simulate bool) { - _, result, abort := anteHandler(ctx, tx, simulate) - require.False(t, abort) - require.Equal(t, sdk.CodeOK, result.Code) - require.True(t, result.IsOK()) -} - -// run the tx through the anteHandler and ensure it fails with the given code -func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, simulate bool, code sdk.CodeType) { - newCtx, result, abort := anteHandler(ctx, tx, simulate) - require.True(t, abort) - require.Equal(t, code, result.Code, fmt.Sprintf("Expected %v, got %v", code, result)) - require.Equal(t, sdk.CodespaceRoot, result.Codespace) - - if code == sdk.CodeOutOfGas { - stdTx, ok := tx.(StdTx) - require.True(t, ok, "tx must be in form auth.StdTx") - // GasWanted set correctly - require.Equal(t, stdTx.Fee.Gas, result.GasWanted, "Gas wanted not set correctly") - require.True(t, result.GasUsed > result.GasWanted, "GasUsed not greated than GasWanted") - // Check that context is set correctly - require.Equal(t, result.GasUsed, newCtx.GasMeter().GasConsumed(), "Context not updated correctly") - } -} - -func newTestTx(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee) sdk.Tx { - sigs := make([]StdSignature, len(privs)) - for i, priv := range privs { - signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") - sig, err := priv.Sign(signBytes) - if err != nil { - panic(err) - } - sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} - } - tx := NewStdTx(msgs, fee, sigs, "") - return tx -} - -func newTestTxWithMemo(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee, memo string) sdk.Tx { - sigs := make([]StdSignature, len(privs)) - for i, priv := range privs { - signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, memo) - sig, err := priv.Sign(signBytes) - if err != nil { - panic(err) - } - sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} - } - tx := NewStdTx(msgs, fee, sigs, memo) - return tx -} - -// All signers sign over the same StdSignDoc. Should always create invalid signatures -func newTestTxWithSignBytes(msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee, signBytes []byte, memo string) sdk.Tx { - sigs := make([]StdSignature, len(privs)) - for i, priv := range privs { - sig, err := priv.Sign(signBytes) - if err != nil { - panic(err) - } - sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} - } - tx := NewStdTx(msgs, fee, sigs, memo) - return tx -} - // Test various error cases in the AnteHandler control flow. func TestAnteHandlerSigErrors(t *testing.T) { // setup @@ -788,4 +695,4 @@ func TestAnteHandlerSigLimitExceeded(t *testing.T) { []uint64{0, 0, 0, 0, 0, 0, 0, 0}, []uint64{0, 0, 0, 0, 0, 0, 0, 0} tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeTooManySignatures) -} \ No newline at end of file +} diff --git a/modules/auth/keeper.go b/modules/auth/keeper.go index 2cee0d866..46ea38749 100644 --- a/modules/auth/keeper.go +++ b/modules/auth/keeper.go @@ -3,7 +3,7 @@ package auth import ( "fmt" - codec "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/codec" sdk "github.com/irisnet/irishub/types" "github.com/tendermint/tendermint/crypto" ) @@ -22,7 +22,6 @@ var ( // This AccountKeeper encodes/decodes accounts using the // go-amino (binary) encoding/decoding library. type AccountKeeper struct { - // The (unexposed) key used to access the store from the Context. key sdk.StoreKey diff --git a/modules/auth/querier.go b/modules/auth/querier.go new file mode 100644 index 000000000..c3d1b04bf --- /dev/null +++ b/modules/auth/querier.go @@ -0,0 +1,57 @@ +package auth + +import ( + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/irisnet/irishub/codec" + sdk "github.com/irisnet/irishub/types" +) + +// query endpoints supported by the auth Querier +const ( + QueryAccount = "account" +) + +// creates a querier for auth REST endpoints +func NewQuerier(keeper AccountKeeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { + switch path[0] { + case QueryAccount: + return queryAccount(ctx, req, keeper) + default: + return nil, sdk.ErrUnknownRequest("unknown auth query endpoint") + } + } +} + +// defines the params for query: "custom/acc/account" +type QueryAccountParams struct { + Address sdk.AccAddress +} + +func NewQueryAccountParams(addr sdk.AccAddress) QueryAccountParams { + return QueryAccountParams{ + Address: addr, + } +} + +func queryAccount(ctx sdk.Context, req abci.RequestQuery, keeper AccountKeeper) ([]byte, sdk.Error) { + var params QueryAccountParams + if err := keeper.cdc.UnmarshalJSON(req.Data, ¶ms); err != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + } + + account := keeper.GetAccount(ctx, params.Address) + if account == nil { + return nil, sdk.ErrUnknownAddress(fmt.Sprintf("account %s does not exist", params.Address)) + } + + bz, err := codec.MarshalJSONIndent(keeper.cdc, account) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + + return bz, nil +} diff --git a/modules/auth/querier_test.go b/modules/auth/querier_test.go new file mode 100644 index 000000000..cbafc1bbf --- /dev/null +++ b/modules/auth/querier_test.go @@ -0,0 +1,41 @@ +package auth + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +func Test_queryAccount(t *testing.T) { + input := setupTestInput() + req := abci.RequestQuery{ + Path: fmt.Sprintf("custom/%s/%s", "acc", QueryAccount), + Data: []byte{}, + } + + res, err := queryAccount(input.ctx, req, input.ak) + require.NotNil(t, err) + require.Nil(t, res) + + req.Data = input.cdc.MustMarshalJSON(NewQueryAccountParams([]byte(""))) + res, err = queryAccount(input.ctx, req, input.ak) + require.NotNil(t, err) + require.Nil(t, res) + + _, _, addr := keyPubAddr() + req.Data = input.cdc.MustMarshalJSON(NewQueryAccountParams(addr)) + res, err = queryAccount(input.ctx, req, input.ak) + require.NotNil(t, err) + require.Nil(t, res) + + input.ak.SetAccount(input.ctx, input.ak.NewAccountWithAddress(input.ctx, addr)) + res, err = queryAccount(input.ctx, req, input.ak) + require.Nil(t, err) + require.NotNil(t, res) + + var account Account + err2 := input.cdc.UnmarshalJSON(res, &account) + require.Nil(t, err2) +} diff --git a/modules/auth/test_utils.go b/modules/auth/test_utils.go new file mode 100644 index 000000000..414b51aee --- /dev/null +++ b/modules/auth/test_utils.go @@ -0,0 +1,151 @@ +// nolint +package auth + +import ( + "fmt" + "testing" + + "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/modules/params" + "github.com/irisnet/irishub/store" + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" +) + +type testInput struct { + cdc *codec.Codec + ctx sdk.Context + ak AccountKeeper + fck FeeKeeper +} + +func setupTestInput() testInput { + db := dbm.NewMemDB() + + cdc := codec.New() + RegisterBaseAccount(cdc) + + authCapKey := sdk.NewKVStoreKey("authCapKey") + fckCapKey := sdk.NewKVStoreKey("fckCapKey") + keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(fckCapKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + ms.LoadLatestVersion() + + pk := params.NewKeeper(cdc, keyParams, tkeyParams) + ak := NewAccountKeeper(cdc, authCapKey, ProtoBaseAccount) + fck := NewFeeKeeper(cdc, fckCapKey, pk.Subspace(DefaultParamSpace)) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "test-chain-id"}, false, log.NewNopLogger()) + + return testInput{cdc: cdc, ctx: ctx, ak: ak, fck: fck} +} + +func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { + key := secp256k1.GenPrivKey() + pub := key.PubKey() + addr := sdk.AccAddress(pub.Address()) + return key, pub, addr +} + +func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { + return sdk.NewTestMsg(addrs...) +} + +func newStdFee() StdFee { + return NewStdFee(5000, + sdk.NewInt64Coin("atom", 150), + ) +} + +// coins to more than cover the fee +func newCoins() sdk.Coins { + return sdk.Coins{ + sdk.NewInt64Coin("atom", 10000000), + } +} + +// generate a priv key and return it with its address +func privAndAddr() (crypto.PrivKey, sdk.AccAddress) { + priv := ed25519.GenPrivKey() + addr := sdk.AccAddress(priv.PubKey().Address()) + return priv, addr +} + +// run the tx through the anteHandler and ensure its valid +func checkValidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, simulate bool) { + _, result, abort := anteHandler(ctx, tx, simulate) + require.False(t, abort) + require.Equal(t, sdk.CodeOK, result.Code) + require.True(t, result.IsOK()) +} + +// run the tx through the anteHandler and ensure it fails with the given code +func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, simulate bool, code sdk.CodeType) { + newCtx, result, abort := anteHandler(ctx, tx, simulate) + require.True(t, abort) + require.Equal(t, code, result.Code, fmt.Sprintf("Expected %v, got %v", code, result)) + require.Equal(t, sdk.CodespaceRoot, result.Codespace) + + if code == sdk.CodeOutOfGas { + stdTx, ok := tx.(StdTx) + require.True(t, ok, "tx must be in form auth.StdTx") + // GasWanted set correctly + require.Equal(t, stdTx.Fee.Gas, result.GasWanted, "Gas wanted not set correctly") + require.True(t, result.GasUsed > result.GasWanted, "GasUsed not greated than GasWanted") + // Check that context is set correctly + require.Equal(t, result.GasUsed, newCtx.GasMeter().GasConsumed(), "Context not updated correctly") + } +} + +func newTestTx(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + tx := NewStdTx(msgs, fee, sigs, "") + return tx +} + +func newTestTxWithMemo(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee, memo string) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, memo) + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + tx := NewStdTx(msgs, fee, sigs, memo) + return tx +} + +// All signers sign over the same StdSignDoc. Should always create invalid signatures +func newTestTxWithSignBytes(msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee StdFee, signBytes []byte, memo string) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + tx := NewStdTx(msgs, fee, sigs, memo) + return tx +}