diff --git a/deployment/localup/localup.sh b/deployment/localup/localup.sh index bbdc9d9bd..625354f40 100644 --- a/deployment/localup/localup.sh +++ b/deployment/localup/localup.sh @@ -180,6 +180,7 @@ function generate_genesis() { echo -e '[[upgrade]]\nname = "Pawnee"\nheight = 23\ninfo = ""' >> ${workspace}/.local/validator${i}/config/app.toml echo -e '[[upgrade]]\nname = "Serengeti"\nheight = 24\ninfo = ""' >> ${workspace}/.local/validator${i}/config/app.toml echo -e '[[upgrade]]\nname = "Erdos"\nheight = 25\ninfo = ""' >> ${workspace}/.local/validator${i}/config/app.toml + echo -e '[payment-check]\nenabled = true\ninterval = 1' >> ${workspace}/.local/validator${i}/config/app.toml done # enable swagger API for validator0 diff --git a/e2e/core/basesuite.go b/e2e/core/basesuite.go index bde8bab85..4e3c4b59f 100644 --- a/e2e/core/basesuite.go +++ b/e2e/core/basesuite.go @@ -177,7 +177,7 @@ func (s *BaseSuite) SetupSuite() { // Create a GVG for each sp by default deposit := sdk.Coin{ Denom: s.Config.Denom, - Amount: types.NewIntFromInt64WithDecimal(1, types.DecimalBNB), + Amount: types.NewIntFromInt64WithDecimal(1000, types.DecimalBNB), } var secondaryIDs []uint32 for _, ssp := range s.StorageProviders { diff --git a/e2e/tests/storage_rate_limit_test.go b/e2e/tests/storage_rate_limit_test.go index 14368d7bc..0017f9e81 100644 --- a/e2e/tests/storage_rate_limit_test.go +++ b/e2e/tests/storage_rate_limit_test.go @@ -89,6 +89,103 @@ func (s *StorageTestSuite) TestSetBucketRateLimitToZero() { s.SendTxBlockWithExpectErrorString(msgCreateObject, user, "greater than the flow rate limit") } +func (s *StorageTestSuite) TestSetBucketRateLimitToZeroAndDelete() { + var err error + sp := s.BaseSuite.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + user := s.User + // CreateBucket + bucketName := storageutils.GenRandomBucketName() + msgCreateBucket := storagetypes.NewMsgCreateBucket( + user.GetAddr(), bucketName, storagetypes.VISIBILITY_TYPE_PUBLIC_READ, 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) + + // HeadBucket + ctx := context.Background() + queryHeadBucketRequest := storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + queryHeadBucketResponse, err := s.Client.HeadBucket(ctx, &queryHeadBucketRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadBucketResponse.BucketInfo.BucketName, bucketName) + s.Require().Equal(queryHeadBucketResponse.BucketInfo.Owner, user.GetAddr().String()) + s.Require().Equal(queryHeadBucketResponse.BucketInfo.GlobalVirtualGroupFamilyId, gvg.FamilyId) + s.Require().Equal(queryHeadBucketResponse.BucketInfo.PaymentAddress, user.GetAddr().String()) + s.Require().Equal(queryHeadBucketResponse.BucketInfo.Visibility, storagetypes.VISIBILITY_TYPE_PUBLIC_READ) + s.Require().Equal(queryHeadBucketResponse.BucketInfo.SourceType, storagetypes.SOURCE_TYPE_ORIGIN) + + queryQuotaUpdateTimeResponse, err := s.Client.QueryQuotaUpdateTime(ctx, &storagetypes.QueryQuoteUpdateTimeRequest{ + BucketName: bucketName, + }) + s.Require().NoError(err) + s.Require().Equal(queryHeadBucketResponse.BucketInfo.CreateAt, queryQuotaUpdateTimeResponse.UpdateAt) + + fmt.Printf("User: %s\n", s.User.GetAddr().String()) + fmt.Printf("queryHeadBucketResponse.BucketInfo.Owner: %s\n", queryHeadBucketResponse.BucketInfo.Owner) + fmt.Printf("queryHeadBucketResponse.BucketInfo.PaymentAccount: %s\n", queryHeadBucketResponse.BucketInfo.PaymentAddress) + + // SetBucketRateLimit + msgSetBucketRateLimit := storagetypes.NewMsgSetBucketFlowRateLimit(s.User.GetAddr(), s.User.GetAddr(), s.User.GetAddr(), bucketName, sdkmath.NewInt(100000000000000000)) + s.SendTxBlock(s.User, msgSetBucketRateLimit) + + // CreateObject + objectName := storageutils.GenRandomObjectName() + // create test buffer + 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() + checksum := sdk.Keccak256(buffer.Bytes()) + expectChecksum := [][]byte{checksum, checksum, checksum, checksum, checksum, checksum, checksum} + contextType := "text/event-stream" + msgCreateObject := storagetypes.NewMsgCreateObject(user.GetAddr(), bucketName, objectName, uint64(payloadSize), storagetypes.VISIBILITY_TYPE_PRIVATE, expectChecksum, contextType, storagetypes.REDUNDANCY_EC_TYPE, math.MaxUint, nil) + msgCreateObject.PrimarySpApproval.Sig, err = sp.ApprovalKey.Sign(msgCreateObject.GetApprovalBytes()) + s.Require().NoError(err) + s.SendTxBlock(s.User, msgCreateObject) + + // HeadObject + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + + // SealObject + gvgId := gvg.Id + msgSealObject := storagetypes.NewMsgSealObject(sp.SealKey.GetAddr(), bucketName, objectName, gvg.Id, nil) + secondarySigs := make([][]byte, 0) + secondarySPBlsPubKeys := make([]bls.PublicKey, 0) + blsSignHash := storagetypes.NewSecondarySpSealObjectSignDoc(s.GetChainID(), gvgId, queryHeadObjectResponse.ObjectInfo.Id, storagetypes.GenerateHash(queryHeadObjectResponse.ObjectInfo.Checksums[:])).GetBlsSignHash() + // every secondary sp signs the checksums + for _, spID := range gvg.SecondarySpIds { + sig, err := core.BlsSignAndVerify(s.StorageProviders[spID], blsSignHash) + s.Require().NoError(err) + secondarySigs = append(secondarySigs, sig) + pk, err := bls.PublicKeyFromBytes(s.StorageProviders[spID].BlsKey.PubKey().Bytes()) + s.Require().NoError(err) + secondarySPBlsPubKeys = append(secondarySPBlsPubKeys, pk) + } + aggBlsSig, err := core.BlsAggregateAndVerify(secondarySPBlsPubKeys, blsSignHash, secondarySigs) + s.Require().NoError(err) + msgSealObject.SecondarySpBlsAggSignatures = aggBlsSig + s.T().Logf("msg %s", msgSealObject.String()) + s.SendTxBlock(sp.SealKey, msgSealObject) + + msgSetBucketRateLimit = storagetypes.NewMsgSetBucketFlowRateLimit(s.User.GetAddr(), s.User.GetAddr(), s.User.GetAddr(), bucketName, sdkmath.NewInt(0)) + s.SendTxBlock(s.User, msgSetBucketRateLimit) + + msgDeleteObject := storagetypes.NewMsgDeleteObject(user.GetAddr(), bucketName, objectName) + s.SendTxBlockWithExpectErrorString(msgDeleteObject, user, "bucket is rate limited") +} + // TestNotOwnerSetBucketRateLimit_Object // 1. user create a bucket with 0 read quota // 2. the payment account set the rate limit diff --git a/x/storage/keeper/keeper.go b/x/storage/keeper/keeper.go index 3b30ff330..6fb53e245 100644 --- a/x/storage/keeper/keeper.go +++ b/x/storage/keeper/keeper.go @@ -2142,6 +2142,13 @@ func (k Keeper) MigrateBucket(ctx sdk.Context, operator sdk.AccAddress, bucketNa return err } + if ctx.IsUpgraded(upgradetypes.Erdos) { + isRateLimited := k.IsBucketRateLimited(ctx, bucketInfo.BucketName) + if isRateLimited { + return fmt.Errorf("bucket is rate limited: %s", bucketInfo.BucketName) + } + } + key := types.GetMigrationBucketKey(bucketInfo.Id) if store.Has(key) { panic("migration bucket key is existed.") diff --git a/x/storage/keeper/payment.go b/x/storage/keeper/payment.go index 462359a3f..8e00e0fb3 100644 --- a/x/storage/keeper/payment.go +++ b/x/storage/keeper/payment.go @@ -445,6 +445,10 @@ func (k Keeper) ChargeViaObjectChange(ctx sdk.Context, bucketInfo *storagetypes. shouldApplyFlowRate = false } } else { + isRateLimited := k.IsBucketRateLimited(ctx, bucketInfo.BucketName) + if isRateLimited { + return nil, fmt.Errorf("bucket is rate limited: %s", bucketInfo.BucketName) + } // we should only check the flow rate limit when is not forced currentBill, err := k.GetBucketReadStoreBill(ctx, bucketInfo, internalBucketInfo) if err != nil { @@ -598,6 +602,11 @@ func (k Keeper) ChargeBucketReadStoreFee(ctx sdk.Context, bucketInfo *storagetyp } if ctx.IsUpgraded(upgradetypes.Erdos) { + isRateLimited := k.IsBucketRateLimited(ctx, bucketInfo.BucketName) + if isRateLimited { + return fmt.Errorf("bucket is rate limited: %s", bucketInfo.BucketName) + } + err := k.isBucketFlowRateUnderLimit(ctx, sdk.MustAccAddressFromHex(bucketInfo.PaymentAddress), sdk.MustAccAddressFromHex(bucketInfo.Owner), bucketInfo.BucketName, bill) if err != nil { return err