Skip to content

Commit

Permalink
refactor(x/authz): set environment in context (cosmos#20502)
Browse files Browse the repository at this point in the history
  • Loading branch information
julienrbrt authored Jun 3, 2024
1 parent ba4bc19 commit 0f81966
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 21 deletions.
8 changes: 8 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,14 @@ To learn more see the [docs](https://docs.cosmos.network/main/learn/advanced/tra

### Modules

<!-- create server/v2 changes docs and mention it here
* mention changes in tx validators
* mention changes with appmodulev2
* mention changes with sdk context removal
* mention changes with environment
* mention changes with environment in context in interfaces
-->

#### `**all**`

* [RFC 001](https://docs.cosmos.network/main/rfc/rfc-001-tx-validation) has defined a simplification of the message validation process for modules.
Expand Down
5 changes: 5 additions & 0 deletions core/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ const (
ExecModeKey contextKey = iota
CometInfoKey contextKey = iota
)

// EnvironmentContextKey is the context key for the environment.
// A caller should not assume the environment is available in each context.
// ref: https://github.com/cosmos/cosmos-sdk/issues/19640
var EnvironmentContextKey = struct{}{}
1 change: 1 addition & 0 deletions x/authz/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### API Breaking Changes

* [#20502](https://github.com/cosmos/cosmos-sdk/pull/20502) `Accept` on the `Authorization` interface now expects the authz environment in the `context.Context`. This is already done when `Accept` is called by `k.DispatchActions`, but should be done manually if `Accept` is called directly.
* [#19783](https://github.com/cosmos/cosmos-sdk/pull/19783) Removes the use of Accounts String() method
* `NewMsgExec`, `NewMsgGrant` and `NewMsgRevoke` now takes strings as arguments instead of `sdk.AccAddress`.
* `ExportGenesis` also returns an error.
Expand Down
9 changes: 6 additions & 3 deletions x/authz/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/cosmos/gogoproto/proto"

"cosmossdk.io/core/appmodule"
corecontext "cosmossdk.io/core/context"
errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/authz"
Expand Down Expand Up @@ -83,8 +84,7 @@ func (k Keeper) update(ctx context.Context, grantee, granter sdk.AccAddress, upd
// grants from the message signer to the grantee.
func (k Keeper) DispatchActions(ctx context.Context, grantee sdk.AccAddress, msgs []sdk.Msg) ([][]byte, error) {
results := make([][]byte, len(msgs))
sdkCtx := sdk.UnwrapSDKContext(ctx)
now := sdkCtx.HeaderInfo().Time
now := k.Environment.HeaderService.HeaderInfo(ctx).Time

for i, msg := range msgs {
signers, _, err := k.cdc.GetMsgSigners(msg)
Expand Down Expand Up @@ -118,7 +118,10 @@ func (k Keeper) DispatchActions(ctx context.Context, grantee sdk.AccAddress, msg
return nil, err
}

resp, err := authorization.Accept(sdkCtx, msg)
// pass the environment in the context
// users on server/v2 are expected to unwrap the environment from the context
// users on baseapp can still unwrap the sdk context
resp, err := authorization.Accept(context.WithValue(ctx, corecontext.EnvironmentContextKey, k.Environment), msg)
if err != nil {
return nil, err
}
Expand Down
6 changes: 2 additions & 4 deletions x/authz/keys.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
package authz

const (
// ModuleName is the module name constant used in many places
ModuleName = "authz"
)
// ModuleName is the module name constant used in many places
const ModuleName = "authz"
34 changes: 33 additions & 1 deletion x/authz/simulation/operations.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package simulation

import (
"context"
"math/rand"
"time"

"cosmossdk.io/core/address"
"cosmossdk.io/core/appmodule"
corecontext "cosmossdk.io/core/context"
coregas "cosmossdk.io/core/gas"
coreheader "cosmossdk.io/core/header"
"cosmossdk.io/x/authz"
"cosmossdk.io/x/authz/keeper"
banktype "cosmossdk.io/x/bank/types"
Expand Down Expand Up @@ -315,7 +320,12 @@ func SimulateMsgExec(

msg := []sdk.Msg{banktype.NewMsgSend(graStr, greStr, coins)}

_, err = sendAuth.Accept(ctx, msg[0])
goCtx := context.WithValue(ctx.Context(), corecontext.EnvironmentContextKey, appmodule.Environment{
HeaderService: headerService{},
GasService: mockGasService{},
})

_, err = sendAuth.Accept(goCtx, msg[0])
if err != nil {
if sdkerrors.ErrInsufficientFunds.Is(err) {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, nil
Expand Down Expand Up @@ -359,3 +369,25 @@ func SimulateMsgExec(
return simtypes.NewOperationMsg(&msgExec, true, "success"), nil, nil
}
}

type headerService struct{}

func (h headerService) HeaderInfo(ctx context.Context) coreheader.Info {
return sdk.UnwrapSDKContext(ctx).HeaderInfo()
}

type mockGasService struct {
coregas.Service
}

func (m mockGasService) GasMeter(ctx context.Context) coregas.Meter {
return mockGasMeter{}
}

type mockGasMeter struct {
coregas.Meter
}

func (m mockGasMeter) Consume(amount coregas.Gas, descriptor string) error {
return nil
}
13 changes: 11 additions & 2 deletions x/bank/types/send_authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"

"cosmossdk.io/core/address"
"cosmossdk.io/core/appmodule/v2"
corecontext "cosmossdk.io/core/context"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/authz"
Expand Down Expand Up @@ -40,12 +42,19 @@ func (a SendAuthorization) Accept(ctx context.Context, msg sdk.Msg) (authz.Accep
return authz.AcceptResponse{}, sdkerrors.ErrInsufficientFunds.Wrapf("requested amount is more than spend limit")
}

authzEnv, ok := ctx.Value(corecontext.EnvironmentContextKey).(appmodule.Environment)
if !ok {
return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrap("environment not set")
}

isAddrExists := false
toAddr := mSend.ToAddress
allowedList := a.GetAllowList()
sdkCtx := sdk.UnwrapSDKContext(ctx)
for _, addr := range allowedList {
sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "send authorization")
if err := authzEnv.GasService.GasMeter(ctx).Consume(gasCostPerIteration, "send authorization"); err != nil {
return authz.AcceptResponse{}, err
}

if addr == toAddr {
isAddrExists = true
break
Expand Down
35 changes: 33 additions & 2 deletions x/bank/types/send_authorization_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package types_test

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/require"

"cosmossdk.io/core/header"
"cosmossdk.io/core/appmodule/v2"
corecontext "cosmossdk.io/core/context"
coregas "cosmossdk.io/core/gas"
coreheader "cosmossdk.io/core/header"
sdkmath "cosmossdk.io/math"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/bank/types"
Expand All @@ -25,9 +29,36 @@ var (
unknownAddrStr = "cosmos1ta047h6lw4hxkmn0wah97h6lta0sml880l"
)

type headerService struct{}

func (h headerService) HeaderInfo(ctx context.Context) coreheader.Info {
return sdk.UnwrapSDKContext(ctx).HeaderInfo()
}

type mockGasService struct {
coregas.Service
}

func (m mockGasService) GasMeter(ctx context.Context) coregas.Meter {
return mockGasMeter{}
}

type mockGasMeter struct {
coregas.Meter
}

func (m mockGasMeter) Consume(amount coregas.Gas, descriptor string) error {
return nil
}

func TestSendAuthorization(t *testing.T) {
ac := codectestutil.CodecOptions{}.GetAddressCodec()
ctx := testutil.DefaultContextWithDB(t, storetypes.NewKVStoreKey(types.StoreKey), storetypes.NewTransientStoreKey("transient_test")).Ctx.WithHeaderInfo(header.Info{})
sdkCtx := testutil.DefaultContextWithDB(t, storetypes.NewKVStoreKey(types.StoreKey), storetypes.NewTransientStoreKey("transient_test")).Ctx.WithHeaderInfo(coreheader.Info{})
ctx := context.WithValue(sdkCtx.Context(), corecontext.EnvironmentContextKey, appmodule.Environment{
HeaderService: headerService{},
GasService: mockGasService{},
})

allowList := make([]sdk.AccAddress, 1)
allowList[0] = toAddr
authorization := types.NewSendAuthorization(coins1000, nil, ac)
Expand Down
17 changes: 14 additions & 3 deletions x/staking/types/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"

"cosmossdk.io/core/address"
"cosmossdk.io/core/appmodule"
corecontext "cosmossdk.io/core/context"
errorsmod "cosmossdk.io/errors"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -100,11 +102,17 @@ func (a StakeAuthorization) Accept(ctx context.Context, msg sdk.Msg) (authz.Acce
return authz.AcceptResponse{}, sdkerrors.ErrInvalidRequest.Wrap("unknown msg type")
}

authzEnv, ok := ctx.Value(corecontext.EnvironmentContextKey).(appmodule.Environment)
if !ok {
return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrap("environment not set")
}
isValidatorExists := false
allowedList := a.GetAllowList().GetAddress()
sdkCtx := sdk.UnwrapSDKContext(ctx)
for _, validator := range allowedList {
sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization")
if err := authzEnv.GasService.GasMeter(ctx).Consume(gasCostPerIteration, "stake authorization"); err != nil {
return authz.AcceptResponse{}, err
}

if validator == validatorAddress {
isValidatorExists = true
break
Expand All @@ -113,7 +121,10 @@ func (a StakeAuthorization) Accept(ctx context.Context, msg sdk.Msg) (authz.Acce

denyList := a.GetDenyList().GetAddress()
for _, validator := range denyList {
sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization")
if err := authzEnv.GasService.GasMeter(ctx).Consume(gasCostPerIteration, "stake authorization"); err != nil {
return authz.AcceptResponse{}, err
}

if validator == validatorAddress {
return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrapf("cannot delegate/undelegate to %s validator", validator)
}
Expand Down
43 changes: 37 additions & 6 deletions x/staking/types/authz_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package types_test

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"cosmossdk.io/core/appmodule/v2"
corecontext "cosmossdk.io/core/context"
coregas "cosmossdk.io/core/gas"
coreheader "cosmossdk.io/core/header"
storetypes "cosmossdk.io/store/types"
stakingtypes "cosmossdk.io/x/staking/types"
Expand Down Expand Up @@ -39,10 +43,37 @@ func accAddressToString(t *testing.T, addr sdk.AccAddress) string {
return r
}

type headerService struct{}

func (h headerService) HeaderInfo(ctx context.Context) coreheader.Info {
return sdk.UnwrapSDKContext(ctx).HeaderInfo()
}

type mockGasService struct {
coregas.Service
}

func (m mockGasService) GasMeter(ctx context.Context) coregas.Meter {
return mockGasMeter{}
}

type mockGasMeter struct {
coregas.Meter
}

func (m mockGasMeter) Consume(amount coregas.Gas, descriptor string) error {
return nil
}

func TestAuthzAuthorizations(t *testing.T) {
key := storetypes.NewKVStoreKey(stakingtypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test"))
ctx := testCtx.Ctx.WithHeaderInfo(coreheader.Info{})
sdkCtx := testCtx.Ctx.WithHeaderInfo(coreheader.Info{})
ctx := context.WithValue(sdkCtx.Context(), corecontext.EnvironmentContextKey, appmodule.Environment{
HeaderService: headerService{},
GasService: mockGasService{},
})

valAddressCodec := codectestutil.CodecOptions{}.GetValidatorCodec()
// verify ValidateBasic returns error for the AUTHORIZATION_TYPE_UNSPECIFIED authorization type
delAuth, err := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_UNSPECIFIED, &coin100, valAddressCodec)
Expand Down Expand Up @@ -313,7 +344,7 @@ func TestAuthzAuthorizations(t *testing.T) {
[]sdk.ValAddress{},
stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION,
&coin100,
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), ctx.HeaderInfo().Height, coin100),
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), sdkCtx.HeaderInfo().Height, coin100),
false,
true,
nil,
Expand All @@ -324,7 +355,7 @@ func TestAuthzAuthorizations(t *testing.T) {
[]sdk.ValAddress{},
stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION,
&coin100,
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), ctx.HeaderInfo().Height, coin50),
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), sdkCtx.HeaderInfo().Height, coin50),
false,
false,
&stakingtypes.StakeAuthorization{
Expand All @@ -341,7 +372,7 @@ func TestAuthzAuthorizations(t *testing.T) {
[]sdk.ValAddress{},
stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION,
&coin100,
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val3), ctx.HeaderInfo().Height, coin50),
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val3), sdkCtx.HeaderInfo().Height, coin50),
true,
false,
nil,
Expand All @@ -352,7 +383,7 @@ func TestAuthzAuthorizations(t *testing.T) {
[]sdk.ValAddress{},
stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION,
nil,
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val2), ctx.HeaderInfo().Height, coin100),
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val2), sdkCtx.HeaderInfo().Height, coin100),
false,
false,
&stakingtypes.StakeAuthorization{
Expand All @@ -369,7 +400,7 @@ func TestAuthzAuthorizations(t *testing.T) {
[]sdk.ValAddress{val1},
stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION,
&coin100,
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), ctx.HeaderInfo().Height, coin100),
stakingtypes.NewMsgCancelUnbondingDelegation(accAddressToString(t, delAddr), valAddressToString(t, val1), sdkCtx.HeaderInfo().Height, coin100),
true,
false,
nil,
Expand Down

0 comments on commit 0f81966

Please sign in to comment.