Skip to content

Commit

Permalink
Merge PR #5877: x/capability: ReleaseCapability
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez authored Mar 27, 2020
2 parents 94f5d6f + 24bd82e commit 3b48464
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 14 deletions.
1 change: 1 addition & 0 deletions x/capability/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var (
KeyPrefixIndexCapability = types.KeyPrefixIndexCapability
ErrCapabilityTaken = types.ErrCapabilityTaken
ErrOwnerClaimed = types.ErrOwnerClaimed
ErrCapabilityNotOwned = types.ErrCapabilityNotOwned
RegisterCodec = types.RegisterCodec
RegisterCapabilityTypeCodec = types.RegisterCapabilityTypeCodec
ModuleCdc = types.ModuleCdc
Expand Down
62 changes: 54 additions & 8 deletions x/capability/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,45 @@ func (sk ScopedKeeper) ClaimCapability(ctx sdk.Context, cap types.Capability, na
return nil
}

// ReleaseCapability allows a scoped module to release a capability which it had
// previously claimed or created. After releasing the capability, if no more
// owners exist, the capability will be globally removed.
func (sk ScopedKeeper) ReleaseCapability(ctx sdk.Context, cap types.Capability) error {
memStore := ctx.KVStore(sk.memKey)

bz := memStore.Get(types.FwdCapabilityKey(sk.module, cap))
if len(bz) == 0 {
return sdkerrors.Wrap(types.ErrCapabilityNotOwned, sk.module)
}

name := string(bz)

// Remove the forward mapping between the module and capability tuple and the
// capability name in the in-memory store.
memStore.Delete(types.FwdCapabilityKey(sk.module, cap))

// Remove the reverse mapping between the module and capability name and the
// capability in the in-memory store.
memStore.Delete(types.RevCapabilityKey(sk.module, name))

// remove owner
capOwners := sk.getOwners(ctx, cap)
capOwners.Remove(types.NewOwner(sk.module, name))

prefixStore := prefix.NewStore(ctx.KVStore(sk.storeKey), types.KeyPrefixIndexCapability)
indexKey := types.IndexToKey(cap.GetIndex())

if len(capOwners.Owners) == 0 {
// remove capability owner set
prefixStore.Delete(indexKey)
} else {
// update capability owner set
prefixStore.Set(indexKey, sk.cdc.MustMarshalBinaryBare(capOwners))
}

return nil
}

// GetCapability allows a module to fetch a capability which it previously claimed
// by name. The module is not allowed to retrieve capabilities which it does not
// own.
Expand All @@ -223,14 +262,7 @@ func (sk ScopedKeeper) addOwner(ctx sdk.Context, cap types.Capability, name stri
prefixStore := prefix.NewStore(ctx.KVStore(sk.storeKey), types.KeyPrefixIndexCapability)
indexKey := types.IndexToKey(cap.GetIndex())

var capOwners *types.CapabilityOwners

bz := prefixStore.Get(indexKey)
if len(bz) == 0 {
capOwners = types.NewCapabilityOwners()
} else {
sk.cdc.MustUnmarshalBinaryBare(bz, &capOwners)
}
capOwners := sk.getOwners(ctx, cap)

if err := capOwners.Set(types.NewOwner(sk.module, name)); err != nil {
return err
Expand All @@ -241,6 +273,20 @@ func (sk ScopedKeeper) addOwner(ctx sdk.Context, cap types.Capability, name stri
return nil
}

func (sk ScopedKeeper) getOwners(ctx sdk.Context, cap types.Capability) (capOwners *types.CapabilityOwners) {
prefixStore := prefix.NewStore(ctx.KVStore(sk.storeKey), types.KeyPrefixIndexCapability)
indexKey := types.IndexToKey(cap.GetIndex())

bz := prefixStore.Get(indexKey)
if len(bz) == 0 {
capOwners = types.NewCapabilityOwners()
} else {
sk.cdc.MustUnmarshalBinaryBare(bz, &capOwners)
}

return capOwners
}

func logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
30 changes: 30 additions & 0 deletions x/capability/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ func (suite *KeeperTestSuite) TestAuthenticateCapability() {
suite.Require().False(sk2.AuthenticateCapability(suite.ctx, cap2, "invalid"))
suite.Require().False(sk2.AuthenticateCapability(suite.ctx, cap1, "bond"))

sk2.ReleaseCapability(suite.ctx, cap2)
suite.Require().False(sk2.AuthenticateCapability(suite.ctx, cap2, "bond"))

badCap := types.NewCapabilityKey(100)
suite.Require().False(sk1.AuthenticateCapability(suite.ctx, badCap, "transfer"))
suite.Require().False(sk2.AuthenticateCapability(suite.ctx, badCap, "bond"))
Expand All @@ -140,6 +143,33 @@ func (suite *KeeperTestSuite) TestClaimCapability() {
suite.Require().Equal(cap, got)
}

func (suite *KeeperTestSuite) TestReleaseCapability() {
sk1 := suite.keeper.ScopeToModule(bank.ModuleName)
sk2 := suite.keeper.ScopeToModule(staking.ModuleName)

cap1, err := sk1.NewCapability(suite.ctx, "transfer")
suite.Require().NoError(err)
suite.Require().NotNil(cap1)

suite.Require().NoError(sk2.ClaimCapability(suite.ctx, cap1, "transfer"))

cap2, err := sk2.NewCapability(suite.ctx, "bond")
suite.Require().NoError(err)
suite.Require().NotNil(cap2)

suite.Require().Error(sk1.ReleaseCapability(suite.ctx, cap2))

suite.Require().NoError(sk2.ReleaseCapability(suite.ctx, cap1))
got, ok := sk2.GetCapability(suite.ctx, "transfer")
suite.Require().False(ok)
suite.Require().Nil(got)

suite.Require().NoError(sk1.ReleaseCapability(suite.ctx, cap1))
got, ok = sk1.GetCapability(suite.ctx, "transfer")
suite.Require().False(ok)
suite.Require().Nil(got)
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}
5 changes: 3 additions & 2 deletions x/capability/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

// x/capability module sentinel errors
var (
ErrCapabilityTaken = sdkerrors.Register(ModuleName, 2, "capability name already taken")
ErrOwnerClaimed = sdkerrors.Register(ModuleName, 3, "given owner already claimed capability")
ErrCapabilityTaken = sdkerrors.Register(ModuleName, 2, "capability name already taken")
ErrOwnerClaimed = sdkerrors.Register(ModuleName, 3, "given owner already claimed capability")
ErrCapabilityNotOwned = sdkerrors.Register(ModuleName, 4, "capability not owned by module")
)
33 changes: 30 additions & 3 deletions x/capability/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,8 @@ func NewCapabilityOwners() *CapabilityOwners {
// already exists, an error will be returned. Set runs in O(log n) average time
// and O(n) in the worst case.
func (co *CapabilityOwners) Set(owner Owner) error {
// find smallest index s.t. co.Owners[i] >= owner in O(log n) time
i := sort.Search(len(co.Owners), func(i int) bool { return co.Owners[i].Key() >= owner.Key() })
if i < len(co.Owners) && co.Owners[i].Key() == owner.Key() {
i, ok := co.Get(owner)
if ok {
// owner already exists at co.Owners[i]
return sdkerrors.Wrapf(ErrOwnerClaimed, owner.String())
}
Expand All @@ -91,3 +90,31 @@ func (co *CapabilityOwners) Set(owner Owner) error {

return nil
}

// Remove removes a provided owner from the CapabilityOwners if it exists. If the
// owner does not exist, Remove is considered a no-op.
func (co *CapabilityOwners) Remove(owner Owner) {
if len(co.Owners) == 0 {
return
}

i, ok := co.Get(owner)
if ok {
// owner exists at co.Owners[i]
co.Owners = append(co.Owners[:i], co.Owners[i+1:]...)
}
}

// Get returns (i, true) of the provided owner in the CapabilityOwners if the
// owner exists, where i indicates the owner's index in the set. Otherwise
// (i, false) where i indicates where in the set the owner should be added.
func (co *CapabilityOwners) Get(owner Owner) (int, bool) {
// find smallest index s.t. co.Owners[i] >= owner in O(log n) time
i := sort.Search(len(co.Owners), func(i int) bool { return co.Owners[i].Key() >= owner.Key() })
if i < len(co.Owners) && co.Owners[i].Key() == owner.Key() {
// owner exists at co.Owners[i]
return i, true
}

return i, false
}
22 changes: 21 additions & 1 deletion x/capability/types/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestOwner(t *testing.T) {
require.Equal(t, "module: bank\nname: send\n", o.String())
}

func TestCapabilityOwners(t *testing.T) {
func TestCapabilityOwners_Set(t *testing.T) {
co := types.NewCapabilityOwners()

owners := make([]types.Owner, 1024)
Expand All @@ -47,3 +47,23 @@ func TestCapabilityOwners(t *testing.T) {
require.Error(t, co.Set(owner))
}
}

func TestCapabilityOwners_Remove(t *testing.T) {
co := types.NewCapabilityOwners()

co.Remove(types.NewOwner("bank", "send-0"))
require.Len(t, co.Owners, 0)

for i := 0; i < 5; i++ {
require.NoError(t, co.Set(types.NewOwner("bank", fmt.Sprintf("send-%d", i))))
}

require.Len(t, co.Owners, 5)

for i := 0; i < 5; i++ {
co.Remove(types.NewOwner("bank", fmt.Sprintf("send-%d", i)))
require.Len(t, co.Owners, 5-(i+1))
}

require.Len(t, co.Owners, 0)
}

0 comments on commit 3b48464

Please sign in to comment.