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 }