2021-01-25 08:41:30 -08:00
|
|
|
package keeper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
|
|
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
|
|
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
2021-10-04 09:36:38 -07:00
|
|
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
2021-01-25 08:41:30 -08:00
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
2021-12-09 20:49:39 -08:00
|
|
|
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
refactor!: BaseApp {Check,Deliver}Tx with middleware design (#9920)
<!--
The default pull request template is for types feat, fix, or refactor.
For other templates, add one of the following parameters to the url:
- template=docs.md
- template=other.md
-->
## Description
ref: #9585
This PR if the 1st step (out of 2) in the #9585 refactor. It transforms baseapp's ABCI {Check,Deliver}Tx into middleware stacks. A middleware is defined by the following interfaces:
```go
// types/tx package
type Handler interface {
CheckTx(ctx context.Context, tx sdk.Tx, req abci.RequestCheckTx) (abci.ResponseCheckTx, error)
DeliverTx(ctx context.Context, tx sdk.Tx, req abci.RequestDeliverTx) (abci.ResponseDeliverTx, error)
SimulateTx(ctx context.Context, tx sdk.Tx, req RequestSimulateTx) (ResponseSimulateTx, error)
}
type Middleware func(Handler) Handler
```
This Pr doesn't migrate antehandlers, but only baseapp's runTx and runMsgs as middlewares. It bundles antehandlers into one single middleware (for now).
More specifically, it introduces the 5 following middlewares:
| Middleware | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| RunMsgsTxMiddleware | This middleware replaces the old baseapp's `runMsgs`. |
| LegacyAnteMiddleware | This middleware is temporary, it bundles all antehandlers into one middleware. It's only created for the purpose of breaking the refactor into 2 pieces. **It will be removed in Part 2**, and each antehandler will be replaced by its own middleware. |
| IndexEventsTxMiddleware | This is a simple middleware that chooses which events to index in Tendermint. Replaces `baseapp.indexEvents` (which unfortunately still exists in baseapp too, because it's used to index Begin/EndBlock events) |
| RecoveryTxMiddleware | This index recovers from panics. It replaces baseapp.runTx's panic recovery. |
| GasTxMiddleware | This replaces the [`Setup`](https://github.com/cosmos/cosmos-sdk/blob/master/x/auth/ante/setup.go) Antehandler. It sets a GasMeter on sdk.Context. Note that before, GasMeter was set on sdk.Context inside the antehandlers, and there was some mess around the fact that antehandlers had their own panic recovery system so that the GasMeter could be read by baseapp's recovery system. Now, this mess is all removed: one middleware sets GasMeter, another one handles recovery. |
---
### Author Checklist
*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*
I have...
- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [x] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [x] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [x] added a changelog entry to `CHANGELOG.md`
- [x] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [x] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed
### Reviewers Checklist
*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*
I have...
- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
2021-08-25 07:40:33 -07:00
|
|
|
"github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
2021-05-06 11:23:48 -07:00
|
|
|
"github.com/cosmos/cosmos-sdk/x/authz"
|
2021-01-25 08:41:30 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
type Keeper struct {
|
2021-12-09 20:49:39 -08:00
|
|
|
storeKey storetypes.StoreKey
|
|
|
|
cdc codec.BinaryCodec
|
|
|
|
router *middleware.MsgServiceRouter
|
|
|
|
authKeeper authkeeper.AccountKeeper
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewKeeper constructs a message authorization Keeper
|
2021-12-09 20:49:39 -08:00
|
|
|
func NewKeeper(storeKey storetypes.StoreKey, cdc codec.BinaryCodec, router *middleware.MsgServiceRouter, ak authkeeper.AccountKeeper) Keeper {
|
2021-01-25 08:41:30 -08:00
|
|
|
return Keeper{
|
2021-12-09 20:49:39 -08:00
|
|
|
storeKey: storeKey,
|
|
|
|
cdc: cdc,
|
|
|
|
router: router,
|
|
|
|
authKeeper: ak,
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Logger returns a module-specific logger.
|
|
|
|
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
|
2021-05-06 11:23:48 -07:00
|
|
|
return ctx.Logger().With("module", fmt.Sprintf("x/%s", authz.ModuleName))
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
2021-05-06 11:23:48 -07:00
|
|
|
// getGrant returns grant stored at skey.
|
|
|
|
func (k Keeper) getGrant(ctx sdk.Context, skey []byte) (grant authz.Grant, found bool) {
|
2021-01-25 08:41:30 -08:00
|
|
|
store := ctx.KVStore(k.storeKey)
|
2021-05-06 11:23:48 -07:00
|
|
|
bz := store.Get(skey)
|
2021-01-25 08:41:30 -08:00
|
|
|
if bz == nil {
|
|
|
|
return grant, false
|
|
|
|
}
|
2021-04-29 03:46:22 -07:00
|
|
|
k.cdc.MustUnmarshal(bz, &grant)
|
2021-01-25 08:41:30 -08:00
|
|
|
return grant, true
|
|
|
|
}
|
|
|
|
|
2021-05-06 11:23:48 -07:00
|
|
|
func (k Keeper) update(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress, updated authz.Authorization) error {
|
|
|
|
skey := grantStoreKey(grantee, granter, updated.MsgTypeURL())
|
|
|
|
grant, found := k.getGrant(ctx, skey)
|
2021-01-25 08:41:30 -08:00
|
|
|
if !found {
|
2021-05-06 11:23:48 -07:00
|
|
|
return sdkerrors.ErrNotFound.Wrap("authorization not found")
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
msg, ok := updated.(proto.Message)
|
|
|
|
if !ok {
|
2021-05-06 11:23:48 -07:00
|
|
|
sdkerrors.ErrPackAny.Wrapf("cannot proto marshal %T", updated)
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
any, err := codectypes.NewAnyWithValue(msg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
grant.Authorization = any
|
|
|
|
store := ctx.KVStore(k.storeKey)
|
2021-05-06 11:23:48 -07:00
|
|
|
store.Set(skey, k.cdc.MustMarshal(&grant))
|
2021-01-25 08:41:30 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DispatchActions attempts to execute the provided messages via authorization
|
|
|
|
// grants from the message signer to the grantee.
|
2021-06-18 12:06:10 -07:00
|
|
|
func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs []sdk.Msg) ([][]byte, error) {
|
|
|
|
var results = make([][]byte, len(msgs))
|
|
|
|
for i, msg := range msgs {
|
2021-04-30 04:00:47 -07:00
|
|
|
signers := msg.GetSigners()
|
2021-01-25 08:41:30 -08:00
|
|
|
if len(signers) != 1 {
|
2021-05-06 11:23:48 -07:00
|
|
|
return nil, sdkerrors.ErrInvalidRequest.Wrap("authorization can be given to msg with only one signer")
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
2021-08-13 08:34:00 -07:00
|
|
|
granter := signers[0]
|
|
|
|
|
2021-05-06 11:23:48 -07:00
|
|
|
// if granter != grantee then check authorization.Accept, otherwise we implicitly accept.
|
2021-01-25 08:41:30 -08:00
|
|
|
if !granter.Equals(grantee) {
|
2021-05-06 11:23:48 -07:00
|
|
|
authorization, _ := k.GetCleanAuthorization(ctx, grantee, granter, sdk.MsgTypeURL(msg))
|
2021-01-25 08:41:30 -08:00
|
|
|
if authorization == nil {
|
2021-05-06 11:23:48 -07:00
|
|
|
return nil, sdkerrors.ErrUnauthorized.Wrap("authorization not found")
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
2021-05-06 11:23:48 -07:00
|
|
|
resp, err := authorization.Accept(ctx, msg)
|
2021-02-19 22:57:57 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
2021-05-06 11:23:48 -07:00
|
|
|
if resp.Delete {
|
|
|
|
err = k.DeleteGrant(ctx, grantee, granter, sdk.MsgTypeURL(msg))
|
|
|
|
} else if resp.Updated != nil {
|
|
|
|
err = k.update(ctx, grantee, granter, resp.Updated)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !resp.Accept {
|
|
|
|
return nil, sdkerrors.ErrUnauthorized
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-18 12:06:10 -07:00
|
|
|
handler := k.router.Handler(msg)
|
2021-01-25 08:41:30 -08:00
|
|
|
if handler == nil {
|
2021-05-06 11:23:48 -07:00
|
|
|
return nil, sdkerrors.ErrUnknownRequest.Wrapf("unrecognized message route: %s", sdk.MsgTypeURL(msg))
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
2021-06-18 12:06:10 -07:00
|
|
|
msgResp, err := handler(ctx, msg)
|
2021-01-25 08:41:30 -08:00
|
|
|
if err != nil {
|
2021-04-30 04:00:47 -07:00
|
|
|
return nil, sdkerrors.Wrapf(err, "failed to execute message; message %v", msg)
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
2021-06-18 12:06:10 -07:00
|
|
|
results[i] = msgResp.Data
|
2021-06-22 12:30:50 -07:00
|
|
|
|
|
|
|
// emit the events from the dispatched actions
|
|
|
|
events := msgResp.Events
|
|
|
|
sdkEvents := make([]sdk.Event, 0, len(events))
|
|
|
|
for i := 0; i < len(events); i++ {
|
|
|
|
sdkEvents = append(sdkEvents, sdk.Event(events[i]))
|
|
|
|
}
|
|
|
|
ctx.EventManager().EmitEvents(sdkEvents)
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
2021-06-18 12:06:10 -07:00
|
|
|
return results, nil
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
2021-05-06 11:23:48 -07:00
|
|
|
// SaveGrant method grants the provided authorization to the grantee on the granter's account
|
|
|
|
// with the provided expiration time. If there is an existing authorization grant for the
|
|
|
|
// same `sdk.Msg` type, this grant overwrites that.
|
|
|
|
func (k Keeper) SaveGrant(ctx sdk.Context, grantee, granter sdk.AccAddress, authorization authz.Authorization, expiration time.Time) error {
|
2021-01-25 08:41:30 -08:00
|
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
|
2021-05-06 11:23:48 -07:00
|
|
|
grant, err := authz.NewGrant(authorization, expiration)
|
2021-01-25 08:41:30 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-04-29 03:46:22 -07:00
|
|
|
bz := k.cdc.MustMarshal(&grant)
|
2021-05-06 11:23:48 -07:00
|
|
|
skey := grantStoreKey(grantee, granter, authorization.MsgTypeURL())
|
|
|
|
store.Set(skey, bz)
|
|
|
|
return ctx.EventManager().EmitTypedEvent(&authz.EventGrant{
|
|
|
|
MsgTypeUrl: authorization.MsgTypeURL(),
|
|
|
|
Granter: granter.String(),
|
|
|
|
Grantee: grantee.String(),
|
|
|
|
})
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
2021-05-06 11:23:48 -07:00
|
|
|
// DeleteGrant revokes any authorization for the provided message type granted to the grantee
|
|
|
|
// by the granter.
|
|
|
|
func (k Keeper) DeleteGrant(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress, msgType string) error {
|
2021-01-25 08:41:30 -08:00
|
|
|
store := ctx.KVStore(k.storeKey)
|
2021-05-06 11:23:48 -07:00
|
|
|
skey := grantStoreKey(grantee, granter, msgType)
|
|
|
|
_, found := k.getGrant(ctx, skey)
|
2021-01-25 08:41:30 -08:00
|
|
|
if !found {
|
2021-05-06 11:23:48 -07:00
|
|
|
return sdkerrors.ErrNotFound.Wrap("authorization not found")
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
2021-05-06 11:23:48 -07:00
|
|
|
store.Delete(skey)
|
|
|
|
return ctx.EventManager().EmitTypedEvent(&authz.EventRevoke{
|
|
|
|
MsgTypeUrl: msgType,
|
|
|
|
Granter: granter.String(),
|
|
|
|
Grantee: grantee.String(),
|
|
|
|
})
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetAuthorizations Returns list of `Authorizations` granted to the grantee by the granter.
|
2021-05-06 11:23:48 -07:00
|
|
|
func (k Keeper) GetAuthorizations(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress) (authorizations []authz.Authorization) {
|
2021-01-25 08:41:30 -08:00
|
|
|
store := ctx.KVStore(k.storeKey)
|
2021-05-06 11:23:48 -07:00
|
|
|
key := grantStoreKey(grantee, granter, "")
|
2021-01-25 08:41:30 -08:00
|
|
|
iter := sdk.KVStorePrefixIterator(store, key)
|
|
|
|
defer iter.Close()
|
2021-05-06 11:23:48 -07:00
|
|
|
var authorization authz.Grant
|
2021-01-25 08:41:30 -08:00
|
|
|
for ; iter.Valid(); iter.Next() {
|
2021-04-29 03:46:22 -07:00
|
|
|
k.cdc.MustUnmarshal(iter.Value(), &authorization)
|
2021-05-06 11:23:48 -07:00
|
|
|
authorizations = append(authorizations, authorization.GetAuthorization())
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
return authorizations
|
|
|
|
}
|
|
|
|
|
2021-05-06 11:23:48 -07:00
|
|
|
// GetCleanAuthorization returns an `Authorization` and it's expiration time for
|
|
|
|
// (grantee, granter, message name) grant. If there is no grant `nil` is returned.
|
|
|
|
// If the grant is expired, the grant is revoked, removed from the storage, and `nil` is returned.
|
|
|
|
func (k Keeper) GetCleanAuthorization(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress, msgType string) (cap authz.Authorization, expiration time.Time) {
|
|
|
|
grant, found := k.getGrant(ctx, grantStoreKey(grantee, granter, msgType))
|
2021-01-25 08:41:30 -08:00
|
|
|
if !found {
|
|
|
|
return nil, time.Time{}
|
|
|
|
}
|
|
|
|
if grant.Expiration.Before(ctx.BlockHeader().Time) {
|
2021-05-06 11:23:48 -07:00
|
|
|
k.DeleteGrant(ctx, grantee, granter, msgType)
|
2021-01-25 08:41:30 -08:00
|
|
|
return nil, time.Time{}
|
|
|
|
}
|
|
|
|
|
2021-05-06 11:23:48 -07:00
|
|
|
return grant.GetAuthorization(), grant.Expiration
|
2021-01-25 08:41:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// IterateGrants iterates over all authorization grants
|
2021-05-06 11:23:48 -07:00
|
|
|
// This function should be used with caution because it can involve significant IO operations.
|
|
|
|
// It should not be used in query or msg services without charging additional gas.
|
2021-01-25 08:41:30 -08:00
|
|
|
func (k Keeper) IterateGrants(ctx sdk.Context,
|
2021-05-06 11:23:48 -07:00
|
|
|
handler func(granterAddr sdk.AccAddress, granteeAddr sdk.AccAddress, grant authz.Grant) bool) {
|
2021-01-25 08:41:30 -08:00
|
|
|
store := ctx.KVStore(k.storeKey)
|
2021-05-06 11:23:48 -07:00
|
|
|
iter := sdk.KVStorePrefixIterator(store, GrantKey)
|
2021-01-25 08:41:30 -08:00
|
|
|
defer iter.Close()
|
|
|
|
for ; iter.Valid(); iter.Next() {
|
2021-05-06 11:23:48 -07:00
|
|
|
var grant authz.Grant
|
|
|
|
granterAddr, granteeAddr := addressesFromGrantStoreKey(iter.Key())
|
2021-04-29 03:46:22 -07:00
|
|
|
k.cdc.MustUnmarshal(iter.Value(), &grant)
|
2021-01-25 08:41:30 -08:00
|
|
|
if handler(granterAddr, granteeAddr, grant) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-06 11:23:48 -07:00
|
|
|
|
|
|
|
// ExportGenesis returns a GenesisState for a given context.
|
|
|
|
func (k Keeper) ExportGenesis(ctx sdk.Context) *authz.GenesisState {
|
|
|
|
var entries []authz.GrantAuthorization
|
|
|
|
k.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, grant authz.Grant) bool {
|
|
|
|
exp := grant.Expiration
|
|
|
|
entries = append(entries, authz.GrantAuthorization{
|
|
|
|
Granter: granter.String(),
|
|
|
|
Grantee: grantee.String(),
|
|
|
|
Expiration: exp,
|
|
|
|
Authorization: grant.Authorization,
|
|
|
|
})
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
|
|
|
|
return authz.NewGenesisState(entries)
|
|
|
|
}
|
|
|
|
|
|
|
|
// InitGenesis new authz genesis
|
|
|
|
func (k Keeper) InitGenesis(ctx sdk.Context, data *authz.GenesisState) {
|
|
|
|
for _, entry := range data.Authorization {
|
|
|
|
grantee, err := sdk.AccAddressFromBech32(entry.Grantee)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
granter, err := sdk.AccAddressFromBech32(entry.Granter)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
a, ok := entry.Authorization.GetCachedValue().(authz.Authorization)
|
|
|
|
if !ok {
|
|
|
|
panic("expected authorization")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = k.SaveGrant(ctx, grantee, granter, a, entry.Expiration)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|