Skip to content

Commit

Permalink
enable delegated agent to create object
Browse files Browse the repository at this point in the history
  • Loading branch information
alexgao001 committed Feb 28, 2024
1 parent a6c913a commit b1af560
Show file tree
Hide file tree
Showing 12 changed files with 2,170 additions and 302 deletions.
4 changes: 4 additions & 0 deletions app/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ func (app *App) registerPawneeUpgradeHandler() {
app.Logger().Info("upgrade to ", plan.Name)
app.GashubKeeper.SetMsgGasParams(ctx, *gashubtypes.NewMsgGasParamsWithFixedGas(sdk.MsgTypeURL(&storagemoduletypes.MsgUpdateObjectContent{}), 1.2e3))
app.GashubKeeper.SetMsgGasParams(ctx, *gashubtypes.NewMsgGasParamsWithFixedGas(sdk.MsgTypeURL(&storagemoduletypes.MsgCancelUpdateObjectContent{}), 1.2e3))

// todo
app.GashubKeeper.SetMsgGasParams(ctx, *gashubtypes.NewMsgGasParamsWithFixedGas(sdk.MsgTypeURL(&storagemoduletypes.MsgUpdateDelegatedAgent{}), 1.2e3))
app.GashubKeeper.SetMsgGasParams(ctx, *gashubtypes.NewMsgGasParamsWithFixedGas(sdk.MsgTypeURL(&storagemoduletypes.MsgDelegateCreateObject{}), 1.2e3))
return app.mm.RunMigrations(ctx, app.configurator, fromVM)
})

Expand Down
6 changes: 4 additions & 2 deletions e2e/tests/permission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,8 @@ func (s *StorageTestSuite) TestStalePermissionForAccountGC() {

// bucket and object dont exist after deletion
headObjectReq := storagetypes.QueryHeadObjectRequest{
BucketName: objectName,
BucketName: bucketName,
ObjectName: objectName,
}
_, err = s.Client.HeadObject(ctx, &headObjectReq)
s.Require().Error(err)
Expand Down Expand Up @@ -1331,7 +1332,8 @@ func (s *StorageTestSuite) TestStalePermissionForGroupGC() {

// bucket and object dont exist after deletion
headObjectReq := storagetypes.QueryHeadObjectRequest{
BucketName: objectName,
BucketName: bucketName,
ObjectName: objectName,
}
_, err = s.Client.HeadObject(ctx, &headObjectReq)
s.Require().Error(err)
Expand Down
152 changes: 152 additions & 0 deletions e2e/tests/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"context"
"fmt"
"github.com/bnb-chain/greenfield/testutil/sample"
permissiontypes "github.com/bnb-chain/greenfield/x/permission/types"
"math"
"reflect"
"strconv"
Expand Down Expand Up @@ -2407,3 +2409,153 @@ func (s *StorageTestSuite) TestDeleteCreateObject_InCreatedStatus() {
_, err = s.Client.HeadObject(ctx, &queryHeadObjectRequest)
s.Require().EqualError(err, "rpc error: code = Unknown desc = No such object: unknown request")
}

func (s *StorageTestSuite) TestUpdateBucketDelegatedAgents() {
var err error
// CreateBucket
sp := s.BaseSuite.PickStorageProvider()
gvg, found := sp.GetFirstGlobalVirtualGroup()
s.Require().True(found)
user := s.GenAndChargeAccounts(1, 1000000)[0]
bucketName := storageutils.GenRandomBucketName()
msgCreateBucket := storagetypes.NewMsgCreateBucket(
user.GetAddr(), bucketName, storagetypes.VISIBILITY_TYPE_PRIVATE, sp.OperatorKey.GetAddr(),
nil, math.MaxUint, nil, 0)
msgCreateBucket.PrimarySpApproval.GlobalVirtualGroupFamilyId = gvg.FamilyId
msgCreateBucket.PrimarySpApproval.Sig, err = sp.ApprovalKey.Sign(msgCreateBucket.GetApprovalBytes())
s.Require().NoError(err)
s.SendTxBlock(user, msgCreateBucket)

queryHeadBucketRequest := storagetypes.QueryHeadBucketRequest{
BucketName: bucketName,
}
ctx := context.Background()
queryHeadBucketResponse, err := s.Client.HeadBucket(ctx, &queryHeadBucketRequest)
s.Require().NoError(err)
s.Require().Equal(0, len(queryHeadBucketResponse.BucketInfo.DelegatedAgentAddresses))

var agentToAdd []sdk.AccAddress
agentToAdd = append(agentToAdd, sp.OperatorKey.GetAddr())
agentAccount := sample.RandAccAddress()
agentToAdd = append(agentToAdd, agentAccount)

msgUpdateDelegatedAgent := storagetypes.NewMsgUpdateDelegatedAgent(
user.GetAddr(),
bucketName,
agentToAdd,
nil)
s.SendTxBlock(user, msgUpdateDelegatedAgent)

// HeadBucket
queryHeadBucketResponse, err = s.Client.HeadBucket(ctx, &queryHeadBucketRequest)
s.Require().NoError(err)
s.Require().Equal(2, len(queryHeadBucketResponse.BucketInfo.DelegatedAgentAddresses))

var agentToRemove []sdk.AccAddress
agentToRemove = append(agentToRemove, sp.OperatorKey.GetAddr())
msgUpdateDelegatedAgent = storagetypes.NewMsgUpdateDelegatedAgent(
user.GetAddr(),
bucketName,
nil,
agentToRemove)
s.SendTxBlock(user, msgUpdateDelegatedAgent)

queryHeadBucketResponse, err = s.Client.HeadBucket(ctx, &queryHeadBucketRequest)
s.Require().NoError(err)
s.Require().Equal(1, len(queryHeadBucketResponse.BucketInfo.DelegatedAgentAddresses))

agentToRemove = []sdk.AccAddress{agentAccount}
msgUpdateDelegatedAgent = storagetypes.NewMsgUpdateDelegatedAgent(
user.GetAddr(),
bucketName,
nil,
agentToRemove)
s.SendTxBlock(user, msgUpdateDelegatedAgent)

queryHeadBucketResponse, err = s.Client.HeadBucket(ctx, &queryHeadBucketRequest)
s.Require().NoError(err)
s.Require().Equal(0, len(queryHeadBucketResponse.BucketInfo.DelegatedAgentAddresses))
}

func (s *StorageTestSuite) TestCreateObjectByDelegatedAgents() {
var err error
ctx := context.Background()

// CreateBucket
sp := s.BaseSuite.PickStorageProvider()
gvg, found := sp.GetFirstGlobalVirtualGroup()
s.Require().True(found)

bucketOwner := s.GenAndChargeAccounts(1, 1000000)[0]
user := s.GenAndChargeAccounts(1, 100)[0]

bucketName := storageutils.GenRandomBucketName()
objectName := storageutils.GenRandomObjectName()

msgCreateBucket := storagetypes.NewMsgCreateBucket(
bucketOwner.GetAddr(), bucketName, storagetypes.VISIBILITY_TYPE_PRIVATE, sp.OperatorKey.GetAddr(),
nil, math.MaxUint, nil, 0)
msgCreateBucket.PrimarySpApproval.GlobalVirtualGroupFamilyId = gvg.FamilyId
msgCreateBucket.PrimarySpApproval.Sig, err = sp.ApprovalKey.Sign(msgCreateBucket.GetApprovalBytes())
s.Require().NoError(err)

var agentToAdd []sdk.AccAddress
agentToAdd = append(agentToAdd, sp.OperatorKey.GetAddr())
msgUpdateDelegatedAgent := storagetypes.NewMsgUpdateDelegatedAgent(
bucketOwner.GetAddr(),
bucketName,
agentToAdd,
nil)
s.SendTxBlock(bucketOwner, msgCreateBucket, msgUpdateDelegatedAgent)

// HeadBucket
queryHeadBucketRequest := storagetypes.QueryHeadBucketRequest{
BucketName: bucketName,
}
queryHeadBucketResponse, err := s.Client.HeadBucket(ctx, &queryHeadBucketRequest)
s.Require().NoError(err)
s.Require().Equal(sp.OperatorKey.GetAddr().String(), queryHeadBucketResponse.BucketInfo.DelegatedAgentAddresses[0])

// DelegateCreate for user2, who does not have permission
var buffer bytes.Buffer
// Create 1MiB content where each line contains 1024 characters.
for i := 0; i < 1024; i++ {
buffer.WriteString(fmt.Sprintf("[%05d] %s\n", i, line))
}
payloadSize := buffer.Len()
contextType := "text/event-stream"
msgDelegateCreateObject := storagetypes.NewMsgDelegateCreateObject(
sp.OperatorKey.GetAddr(),
user.GetAddr(),
bucketName,
objectName,
uint64(payloadSize),
storagetypes.VISIBILITY_TYPE_PRIVATE,
nil,
contextType,
storagetypes.REDUNDANCY_EC_TYPE)
s.SendTxBlockWithExpectErrorString(msgDelegateCreateObject, sp.OperatorKey, "has no CreateObject permission of the bucket")

statement := &permissiontypes.Statement{
Actions: []permissiontypes.ActionType{permissiontypes.ACTION_CREATE_OBJECT},
Effect: permissiontypes.EFFECT_ALLOW,
}
principal := permissiontypes.NewPrincipalWithAccount(user.GetAddr())
msgPutPolicy := storagetypes.NewMsgPutPolicy(bucketOwner.GetAddr(), types2.NewBucketGRN(bucketName).String(),
principal, []*permissiontypes.Statement{statement}, nil)
s.SendTxBlock(bucketOwner, msgPutPolicy)

s.SendTxBlock(sp.OperatorKey, msgDelegateCreateObject)

headObjectReq := storagetypes.QueryHeadObjectRequest{
BucketName: bucketName,
ObjectName: objectName,
}
headObjectResp, err := s.Client.HeadObject(ctx, &headObjectReq)
s.Require().NoError(err)
s.Require().Equal(objectName, headObjectResp.ObjectInfo.ObjectName)
s.Require().Equal(user.GetAddr().String(), headObjectResp.ObjectInfo.Creator)
s.Require().Equal(bucketOwner.GetAddr().String(), headObjectResp.ObjectInfo.Owner)
s.Require().Equal(0, len(headObjectResp.ObjectInfo.Checksums))

}
51 changes: 51 additions & 0 deletions proto/greenfield/storage/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ service Msg {
rpc UpdateBucketInfo(MsgUpdateBucketInfo) returns (MsgUpdateBucketInfoResponse);
rpc MirrorBucket(MsgMirrorBucket) returns (MsgMirrorBucketResponse);
rpc DiscontinueBucket(MsgDiscontinueBucket) returns (MsgDiscontinueBucketResponse);
rpc UpdateDelegatedAgent(MsgUpdateDelegatedAgent) returns (MsgUpdateDelegatedAgentResponse);

// basic operation of object
rpc CreateObject(MsgCreateObject) returns (MsgCreateObjectResponse);
Expand All @@ -36,6 +37,7 @@ service Msg {
rpc UpdateObjectInfo(MsgUpdateObjectInfo) returns (MsgUpdateObjectInfoResponse);
rpc UpdateObjectContent(MsgUpdateObjectContent) returns (MsgUpdateObjectContentResponse);
rpc CancelUpdateObjectContent(MsgCancelUpdateObjectContent) returns (MsgCancelUpdateObjectContentResponse);
rpc DelegateCreateObject(MsgDelegateCreateObject) returns (MsgDelegateCreateObjectResponse);

// basic operation of group
rpc CreateGroup(MsgCreateGroup) returns (MsgCreateGroupResponse);
Expand Down Expand Up @@ -679,3 +681,52 @@ message MsgCancelUpdateObjectContent {
}

message MsgCancelUpdateObjectContentResponse {}


message MsgDelegateCreateObject {
option (cosmos.msg.v1.signer) = "operator";
// operator defines the account address of the operator, it is the delegated agent that allows to creat object under bucket.
string operator = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// creator defines the account address of the object creator.
string creator = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// bucket_name defines the name of the bucket where the object is stored.
string bucket_name = 3;
// object_name defines the name of object
string object_name = 4;
// payload_size defines size of the object's payload
uint64 payload_size = 5;
// content_type define the format of the object which should be a standard MIME type.
string content_type = 6;
// visibility means the object is private or public. if private, only object owner or grantee can access it,
// otherwise every greenfield user can access it.
VisibilityType visibility = 7;
// expect_checksums defines a list of hashes which was generate by redundancy algorithm.
repeated bytes expect_checksums = 8;
// redundancy_type can be ec or replica
RedundancyType redundancy_type = 9;
}

message MsgDelegateCreateObjectResponse {
string object_id = 1 [
(cosmos_proto.scalar) = "cosmos.Uint",
(gogoproto.customtype) = "Uint",
(gogoproto.nullable) = false
];
}


message MsgUpdateDelegatedAgent {
option (cosmos.msg.v1.signer) = "operator";

// operator defines the account address of the operator, only the bucket owner can update the delegated agent.
string operator = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// bucket_name defines the name of the bucket.
string bucket_name = 2;
// agents_to_add defines the delegated agent addresses to be added
repeated string agents_to_add = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// agents_to_remove defines the delegated agent addresses to be removed
repeated string agents_to_remove = 4 [(cosmos_proto.scalar) = "cosmos.AddressString"];
}

message MsgUpdateDelegatedAgentResponse {
}
2 changes: 2 additions & 0 deletions proto/greenfield/storage/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ message BucketInfo {
BucketStatus bucket_status = 10;
// tags defines a list of tags the bucket has
ResourceTags tags = 11;
// delegated_agent_addresses
repeated string delegated_agent_addresses = 12 [(cosmos_proto.scalar) = "cosmos.AddressString"];
}

message InternalBucketInfo {
Expand Down
119 changes: 119 additions & 0 deletions x/storage/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,125 @@ func (k Keeper) CreateObject(
return objectInfo.Id, nil
}

func (k Keeper) DelegateCreateObject(
ctx sdk.Context, delegatee, creator sdk.AccAddress, bucketName, objectName string, payloadSize uint64,
opts types.CreateObjectOptions,
) (sdkmath.Uint, error) {
store := ctx.KVStore(k.storeKey)

// check payload size
if payloadSize > k.MaxPayloadSize(ctx) {
return sdkmath.ZeroUint(), types.ErrTooLargeObject
}

// check bucket
bucketInfo, found := k.GetBucketInfo(ctx, bucketName)
if !found {
return sdkmath.ZeroUint(), types.ErrNoSuchBucket
}
err := bucketInfo.CheckBucketStatus()
if err != nil {
return sdkmath.ZeroUint(), err
}

// primary sp
sp := k.MustGetPrimarySPForBucket(ctx, bucketInfo)

allowed := false
_ = false
// check if the delegated agent is the primary SP.
for _, delegatedAgent := range bucketInfo.DelegatedAgentAddresses {
if delegatee.String() == delegatedAgent {
allowed = true
if delegatee.String() == sp.OperatorAddress {
_ = true
}
}
}
if !allowed {
return sdkmath.ZeroUint(), fmt.Errorf("the delegatee address is not allowed to create object")
}

// verify permission of creator
verifyOpts := &permtypes.VerifyOptions{
WantedSize: &payloadSize,
}
effect := k.VerifyBucketPermission(ctx, bucketInfo, creator, permtypes.ACTION_CREATE_OBJECT, verifyOpts)
if effect != permtypes.EFFECT_ALLOW {
return sdkmath.ZeroUint(), types.ErrAccessDenied.Wrapf("The creator(%s) has no CreateObject permission of the bucket(%s)",
creator.String(), bucketName)
}
objectKey := types.GetObjectKey(bucketName, objectName)
if store.Has(objectKey) {
return sdkmath.ZeroUint(), types.ErrObjectAlreadyExists
}
// check payload size, the empty object doesn't need sealed
var objectStatus types.ObjectStatus
if payloadSize == 0 {
// empty object does not interact with sp
objectStatus = types.OBJECT_STATUS_SEALED
} else {
objectStatus = types.OBJECT_STATUS_CREATED
}
// construct objectInfo
objectInfo := types.ObjectInfo{
Owner: bucketInfo.Owner,
Creator: creator.String(),
BucketName: bucketName,
ObjectName: objectName,
PayloadSize: payloadSize,
Visibility: opts.Visibility,
ContentType: opts.ContentType,
Id: k.GenNextObjectID(ctx),
CreateAt: ctx.BlockTime().Unix(),
ObjectStatus: objectStatus,
RedundancyType: opts.RedundancyType,
SourceType: opts.SourceType,
Checksums: opts.Checksums,
}
if objectInfo.PayloadSize == 0 {
_, err := k.SealEmptyObjectOnVirtualGroup(ctx, bucketInfo, &objectInfo)
if err != nil {
return sdkmath.ZeroUint(), err
}
} else {
// Lock Fee
err = k.LockObjectStoreFee(ctx, bucketInfo, &objectInfo)
if err != nil {
return sdkmath.ZeroUint(), err
}
}

bbz := k.cdc.MustMarshal(bucketInfo)
store.Set(types.GetBucketByIDKey(bucketInfo.Id), bbz)

obz := k.cdc.MustMarshal(&objectInfo)
store.Set(objectKey, k.objectSeq.EncodeSequence(objectInfo.Id))
store.Set(types.GetObjectByIDKey(objectInfo.Id), obz)

if err = ctx.EventManager().EmitTypedEvents(&types.EventCreateObject{
Creator: creator.String(),
Owner: objectInfo.Owner,
BucketName: bucketInfo.BucketName,
ObjectName: objectInfo.ObjectName,
BucketId: bucketInfo.Id,
ObjectId: objectInfo.Id,
CreateAt: objectInfo.CreateAt,
PayloadSize: objectInfo.PayloadSize,
Visibility: objectInfo.Visibility,
PrimarySpId: sp.Id,
ContentType: objectInfo.ContentType,
Status: objectInfo.ObjectStatus,
RedundancyType: objectInfo.RedundancyType,
SourceType: objectInfo.SourceType,
Checksums: objectInfo.Checksums,
LocalVirtualGroupId: objectInfo.LocalVirtualGroupId,
}); err != nil {
return objectInfo.Id, err
}
return objectInfo.Id, nil
}

// StoreObjectInfo stores object related keys to KVStore,
// it's designed to be used in tests
func (k Keeper) StoreObjectInfo(ctx sdk.Context, objectInfo *types.ObjectInfo) {
Expand Down
Loading

0 comments on commit b1af560

Please sign in to comment.