github.com/cosmos/cosmos-sdk@v0.50.10/x/authz/keeper/keeper.go (about)

     1  package keeper
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"strconv"
     8  	"time"
     9  
    10  	abci "github.com/cometbft/cometbft/abci/types"
    11  	"github.com/cosmos/gogoproto/proto"
    12  
    13  	corestoretypes "cosmossdk.io/core/store"
    14  	errorsmod "cosmossdk.io/errors"
    15  	"cosmossdk.io/log"
    16  	storetypes "cosmossdk.io/store/types"
    17  
    18  	"github.com/cosmos/cosmos-sdk/baseapp"
    19  	"github.com/cosmos/cosmos-sdk/codec"
    20  	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
    21  	"github.com/cosmos/cosmos-sdk/runtime"
    22  	sdk "github.com/cosmos/cosmos-sdk/types"
    23  	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
    24  	"github.com/cosmos/cosmos-sdk/x/authz"
    25  )
    26  
    27  // TODO: Revisit this once we have propoer gas fee framework.
    28  // Tracking issues https://github.com/cosmos/cosmos-sdk/issues/9054,
    29  // https://github.com/cosmos/cosmos-sdk/discussions/9072
    30  const gasCostPerIteration = uint64(20)
    31  
    32  type Keeper struct {
    33  	storeService corestoretypes.KVStoreService
    34  	cdc          codec.Codec
    35  	router       baseapp.MessageRouter
    36  	authKeeper   authz.AccountKeeper
    37  	bankKeeper   authz.BankKeeper
    38  }
    39  
    40  // NewKeeper constructs a message authorization Keeper
    41  func NewKeeper(storeService corestoretypes.KVStoreService, cdc codec.Codec, router baseapp.MessageRouter, ak authz.AccountKeeper) Keeper {
    42  	return Keeper{
    43  		storeService: storeService,
    44  		cdc:          cdc,
    45  		router:       router,
    46  		authKeeper:   ak,
    47  	}
    48  }
    49  
    50  // Super ugly hack to not be breaking in v0.50 and v0.47
    51  // DO NOT USE.
    52  func (k Keeper) SetBankKeeper(bk authz.BankKeeper) Keeper {
    53  	k.bankKeeper = bk
    54  	return k
    55  }
    56  
    57  // Logger returns a module-specific logger.
    58  func (k Keeper) Logger(ctx context.Context) log.Logger {
    59  	sdkCtx := sdk.UnwrapSDKContext(ctx)
    60  	return sdkCtx.Logger().With("module", fmt.Sprintf("x/%s", authz.ModuleName))
    61  }
    62  
    63  // getGrant returns grant stored at skey.
    64  func (k Keeper) getGrant(ctx context.Context, skey []byte) (grant authz.Grant, found bool) {
    65  	store := k.storeService.OpenKVStore(ctx)
    66  
    67  	bz, err := store.Get(skey)
    68  	if err != nil {
    69  		panic(err)
    70  	}
    71  
    72  	if bz == nil {
    73  		return grant, false
    74  	}
    75  	k.cdc.MustUnmarshal(bz, &grant)
    76  	return grant, true
    77  }
    78  
    79  func (k Keeper) update(ctx context.Context, grantee, granter sdk.AccAddress, updated authz.Authorization) error {
    80  	skey := grantStoreKey(grantee, granter, updated.MsgTypeURL())
    81  	grant, found := k.getGrant(ctx, skey)
    82  	if !found {
    83  		return authz.ErrNoAuthorizationFound
    84  	}
    85  
    86  	msg, ok := updated.(proto.Message)
    87  	if !ok {
    88  		return sdkerrors.ErrPackAny.Wrapf("cannot proto marshal %T", updated)
    89  	}
    90  
    91  	any, err := codectypes.NewAnyWithValue(msg)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	grant.Authorization = any
    97  	store := k.storeService.OpenKVStore(ctx)
    98  	store.Set(skey, k.cdc.MustMarshal(&grant))
    99  
   100  	return nil
   101  }
   102  
   103  // DispatchActions attempts to execute the provided messages via authorization
   104  // grants from the message signer to the grantee.
   105  func (k Keeper) DispatchActions(ctx context.Context, grantee sdk.AccAddress, msgs []sdk.Msg) ([][]byte, error) {
   106  	results := make([][]byte, len(msgs))
   107  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   108  	now := sdkCtx.BlockTime()
   109  
   110  	for i, msg := range msgs {
   111  		signers, _, err := k.cdc.GetMsgV1Signers(msg)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  
   116  		if len(signers) != 1 {
   117  			return nil, authz.ErrAuthorizationNumOfSigners
   118  		}
   119  
   120  		granter := signers[0]
   121  
   122  		// If granter != grantee then check authorization.Accept, otherwise we
   123  		// implicitly accept.
   124  		if !bytes.Equal(granter, grantee) {
   125  			skey := grantStoreKey(grantee, granter, sdk.MsgTypeURL(msg))
   126  
   127  			grant, found := k.getGrant(ctx, skey)
   128  			if !found {
   129  				return nil, errorsmod.Wrapf(authz.ErrNoAuthorizationFound,
   130  					"failed to get grant with given granter: %s, grantee: %s & msgType: %s ", sdk.AccAddress(granter), grantee, sdk.MsgTypeURL(msg))
   131  			}
   132  
   133  			if grant.Expiration != nil && grant.Expiration.Before(now) {
   134  				return nil, authz.ErrAuthorizationExpired
   135  			}
   136  
   137  			authorization, err := grant.GetAuthorization()
   138  			if err != nil {
   139  				return nil, err
   140  			}
   141  
   142  			resp, err := authorization.Accept(sdkCtx, msg)
   143  			if err != nil {
   144  				return nil, err
   145  			}
   146  
   147  			if resp.Delete {
   148  				err = k.DeleteGrant(ctx, grantee, granter, sdk.MsgTypeURL(msg))
   149  			} else if resp.Updated != nil {
   150  				err = k.update(ctx, grantee, granter, resp.Updated)
   151  			}
   152  			if err != nil {
   153  				return nil, err
   154  			}
   155  
   156  			if !resp.Accept {
   157  				return nil, sdkerrors.ErrUnauthorized
   158  			}
   159  		}
   160  
   161  		handler := k.router.Handler(msg)
   162  		if handler == nil {
   163  			return nil, sdkerrors.ErrUnknownRequest.Wrapf("unrecognized message route: %s", sdk.MsgTypeURL(msg))
   164  		}
   165  
   166  		msgResp, err := handler(sdkCtx, msg)
   167  		if err != nil {
   168  			return nil, errorsmod.Wrapf(err, "failed to execute message; message %v", msg)
   169  		}
   170  
   171  		results[i] = msgResp.Data
   172  
   173  		// emit the events from the dispatched actions
   174  		events := msgResp.Events
   175  		sdkEvents := make([]sdk.Event, 0, len(events))
   176  		for _, event := range events {
   177  			e := event
   178  			e.Attributes = append(e.Attributes, abci.EventAttribute{Key: "authz_msg_index", Value: strconv.Itoa(i)})
   179  
   180  			sdkEvents = append(sdkEvents, sdk.Event(e))
   181  		}
   182  
   183  		sdkCtx.EventManager().EmitEvents(sdkEvents)
   184  	}
   185  
   186  	return results, nil
   187  }
   188  
   189  // SaveGrant method grants the provided authorization to the grantee on the granter's account
   190  // with the provided expiration time and insert authorization key into the grants queue. If there is an existing authorization grant for the
   191  // same `sdk.Msg` type, this grant overwrites that.
   192  func (k Keeper) SaveGrant(ctx context.Context, grantee, granter sdk.AccAddress, authorization authz.Authorization, expiration *time.Time) error {
   193  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   194  	msgType := authorization.MsgTypeURL()
   195  	store := k.storeService.OpenKVStore(ctx)
   196  	skey := grantStoreKey(grantee, granter, msgType)
   197  
   198  	grant, err := authz.NewGrant(sdkCtx.BlockTime(), authorization, expiration)
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	var oldExp *time.Time
   204  	if oldGrant, found := k.getGrant(ctx, skey); found {
   205  		oldExp = oldGrant.Expiration
   206  	}
   207  
   208  	if oldExp != nil && (expiration == nil || !oldExp.Equal(*expiration)) {
   209  		if err = k.removeFromGrantQueue(ctx, skey, granter, grantee, *oldExp); err != nil {
   210  			return err
   211  		}
   212  	}
   213  
   214  	// If the expiration didn't change, then we don't remove it and we should not insert again
   215  	if expiration != nil && (oldExp == nil || !oldExp.Equal(*expiration)) {
   216  		if err = k.insertIntoGrantQueue(ctx, granter, grantee, msgType, *expiration); err != nil {
   217  			return err
   218  		}
   219  	}
   220  
   221  	bz, err := k.cdc.Marshal(&grant)
   222  	if err != nil {
   223  		return err
   224  	}
   225  
   226  	err = store.Set(skey, bz)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	return sdkCtx.EventManager().EmitTypedEvent(&authz.EventGrant{
   232  		MsgTypeUrl: authorization.MsgTypeURL(),
   233  		Granter:    granter.String(),
   234  		Grantee:    grantee.String(),
   235  	})
   236  }
   237  
   238  // DeleteGrant revokes any authorization for the provided message type granted to the grantee
   239  // by the granter.
   240  func (k Keeper) DeleteGrant(ctx context.Context, grantee, granter sdk.AccAddress, msgType string) error {
   241  	store := k.storeService.OpenKVStore(ctx)
   242  	skey := grantStoreKey(grantee, granter, msgType)
   243  	grant, found := k.getGrant(ctx, skey)
   244  	if !found {
   245  		return errorsmod.Wrapf(authz.ErrNoAuthorizationFound, "failed to delete grant with key %s", string(skey))
   246  	}
   247  
   248  	if grant.Expiration != nil {
   249  		err := k.removeFromGrantQueue(ctx, skey, granter, grantee, *grant.Expiration)
   250  		if err != nil {
   251  			return err
   252  		}
   253  	}
   254  
   255  	err := store.Delete(skey)
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   261  	return sdkCtx.EventManager().EmitTypedEvent(&authz.EventRevoke{
   262  		MsgTypeUrl: msgType,
   263  		Granter:    granter.String(),
   264  		Grantee:    grantee.String(),
   265  	})
   266  }
   267  
   268  // GetAuthorizations Returns list of `Authorizations` granted to the grantee by the granter.
   269  func (k Keeper) GetAuthorizations(ctx context.Context, grantee, granter sdk.AccAddress) ([]authz.Authorization, error) {
   270  	store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
   271  	key := grantStoreKey(grantee, granter, "")
   272  	iter := storetypes.KVStorePrefixIterator(store, key)
   273  	defer iter.Close()
   274  
   275  	var authorizations []authz.Authorization
   276  	for ; iter.Valid(); iter.Next() {
   277  		var authorization authz.Grant
   278  		if err := k.cdc.Unmarshal(iter.Value(), &authorization); err != nil {
   279  			return nil, err
   280  		}
   281  
   282  		a, err := authorization.GetAuthorization()
   283  		if err != nil {
   284  			return nil, err
   285  		}
   286  
   287  		authorizations = append(authorizations, a)
   288  	}
   289  
   290  	return authorizations, nil
   291  }
   292  
   293  // GetAuthorization returns an Authorization and it's expiration time.
   294  // A nil Authorization is returned under the following circumstances:
   295  //   - No grant is found.
   296  //   - A grant is found, but it is expired.
   297  //   - There was an error getting the authorization from the grant.
   298  func (k Keeper) GetAuthorization(ctx context.Context, grantee, granter sdk.AccAddress, msgType string) (authz.Authorization, *time.Time) {
   299  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   300  	grant, found := k.getGrant(ctx, grantStoreKey(grantee, granter, msgType))
   301  	if !found || (grant.Expiration != nil && grant.Expiration.Before(sdkCtx.BlockHeader().Time)) {
   302  		return nil, nil
   303  	}
   304  
   305  	auth, err := grant.GetAuthorization()
   306  	if err != nil {
   307  		return nil, nil
   308  	}
   309  
   310  	return auth, grant.Expiration
   311  }
   312  
   313  // IterateGrants iterates over all authorization grants
   314  // This function should be used with caution because it can involve significant IO operations.
   315  // It should not be used in query or msg services without charging additional gas.
   316  // The iteration stops when the handler function returns true or the iterator exhaust.
   317  func (k Keeper) IterateGrants(ctx context.Context,
   318  	handler func(granterAddr, granteeAddr sdk.AccAddress, grant authz.Grant) bool,
   319  ) {
   320  	store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
   321  	iter := storetypes.KVStorePrefixIterator(store, GrantKey)
   322  	defer iter.Close()
   323  	for ; iter.Valid(); iter.Next() {
   324  		var grant authz.Grant
   325  		granterAddr, granteeAddr, _ := parseGrantStoreKey(iter.Key())
   326  		k.cdc.MustUnmarshal(iter.Value(), &grant)
   327  		if handler(granterAddr, granteeAddr, grant) {
   328  			break
   329  		}
   330  	}
   331  }
   332  
   333  func (k Keeper) getGrantQueueItem(ctx context.Context, expiration time.Time, granter, grantee sdk.AccAddress) (*authz.GrantQueueItem, error) {
   334  	store := k.storeService.OpenKVStore(ctx)
   335  	bz, err := store.Get(GrantQueueKey(expiration, granter, grantee))
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  
   340  	if bz == nil {
   341  		return &authz.GrantQueueItem{}, nil
   342  	}
   343  
   344  	var queueItems authz.GrantQueueItem
   345  	if err := k.cdc.Unmarshal(bz, &queueItems); err != nil {
   346  		return nil, err
   347  	}
   348  	return &queueItems, nil
   349  }
   350  
   351  func (k Keeper) setGrantQueueItem(ctx context.Context, expiration time.Time,
   352  	granter, grantee sdk.AccAddress, queueItems *authz.GrantQueueItem,
   353  ) error {
   354  	store := k.storeService.OpenKVStore(ctx)
   355  	bz, err := k.cdc.Marshal(queueItems)
   356  	if err != nil {
   357  		return err
   358  	}
   359  	return store.Set(GrantQueueKey(expiration, granter, grantee), bz)
   360  }
   361  
   362  // insertIntoGrantQueue inserts a grant key into the grant queue
   363  func (k Keeper) insertIntoGrantQueue(ctx context.Context, granter, grantee sdk.AccAddress, msgType string, expiration time.Time) error {
   364  	queueItems, err := k.getGrantQueueItem(ctx, expiration, granter, grantee)
   365  	if err != nil {
   366  		return err
   367  	}
   368  
   369  	queueItems.MsgTypeUrls = append(queueItems.MsgTypeUrls, msgType)
   370  	return k.setGrantQueueItem(ctx, expiration, granter, grantee, queueItems)
   371  }
   372  
   373  // removeFromGrantQueue removes a grant key from the grant queue
   374  func (k Keeper) removeFromGrantQueue(ctx context.Context, grantKey []byte, granter, grantee sdk.AccAddress, expiration time.Time) error {
   375  	store := k.storeService.OpenKVStore(ctx)
   376  	key := GrantQueueKey(expiration, granter, grantee)
   377  	bz, err := store.Get(key)
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	if bz == nil {
   383  		return errorsmod.Wrap(authz.ErrNoGrantKeyFound, "can't remove grant from the expire queue, grant key not found")
   384  	}
   385  
   386  	var queueItem authz.GrantQueueItem
   387  	if err := k.cdc.Unmarshal(bz, &queueItem); err != nil {
   388  		return err
   389  	}
   390  
   391  	_, _, msgType := parseGrantStoreKey(grantKey)
   392  	queueItems := queueItem.MsgTypeUrls
   393  
   394  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   395  	for index, typeURL := range queueItems {
   396  		sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "grant queue")
   397  
   398  		if typeURL == msgType {
   399  			end := len(queueItem.MsgTypeUrls) - 1
   400  			queueItems[index] = queueItems[end]
   401  			queueItems = queueItems[:end]
   402  
   403  			if err := k.setGrantQueueItem(ctx, expiration, granter, grantee, &authz.GrantQueueItem{
   404  				MsgTypeUrls: queueItems,
   405  			}); err != nil {
   406  				return err
   407  			}
   408  			break
   409  		}
   410  	}
   411  
   412  	return nil
   413  }
   414  
   415  // DequeueAndDeleteExpiredGrants deletes expired grants from the state and grant queue.
   416  func (k Keeper) DequeueAndDeleteExpiredGrants(ctx context.Context) error {
   417  	store := k.storeService.OpenKVStore(ctx)
   418  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   419  
   420  	iterator, err := store.Iterator(GrantQueuePrefix, storetypes.InclusiveEndBytes(GrantQueueTimePrefix(sdkCtx.BlockTime())))
   421  	if err != nil {
   422  		return err
   423  	}
   424  	defer iterator.Close()
   425  
   426  	for ; iterator.Valid(); iterator.Next() {
   427  		var queueItem authz.GrantQueueItem
   428  		if err := k.cdc.Unmarshal(iterator.Value(), &queueItem); err != nil {
   429  			return err
   430  		}
   431  
   432  		_, granter, grantee, err := parseGrantQueueKey(iterator.Key())
   433  		if err != nil {
   434  			return err
   435  		}
   436  
   437  		err = store.Delete(iterator.Key())
   438  		if err != nil {
   439  			return err
   440  		}
   441  
   442  		for _, typeURL := range queueItem.MsgTypeUrls {
   443  			err = store.Delete(grantStoreKey(grantee, granter, typeURL))
   444  			if err != nil {
   445  				return err
   446  			}
   447  		}
   448  	}
   449  
   450  	return nil
   451  }