diff --git a/app/reconciliation.go b/app/reconciliation.go index cfc9334fd..f6f2d34a2 100644 --- a/app/reconciliation.go +++ b/app/reconciliation.go @@ -3,13 +3,15 @@ package app import ( "bytes" "encoding/binary" + "encoding/json" "fmt" "cosmossdk.io/math" - paymenttypes "github.com/bnb-chain/greenfield/x/payment/types" "github.com/cosmos/cosmos-sdk/store/iavl" sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + paymenttypes "github.com/bnb-chain/greenfield/x/payment/types" ) const reconStoreKey = "reconciliation" @@ -124,6 +126,9 @@ func (app *App) reconPaymentChanges(ctx sdk.Context, paymentIavl *iavl.Store) bo ctx.Logger().Error("fail to unmarshal stream record", "err", err.Error()) } else { flowCurrent = flowCurrent.Add(sr.NetflowRate) + //TODO: impact performance, remove it later + j, _ := json.Marshal(sr) + ctx.Logger().Debug("stream_record_current", "stream record", j, "addr", parseAddressFromStreamRecordKey(kBz)) } } @@ -139,6 +144,9 @@ func (app *App) reconPaymentChanges(ctx sdk.Context, paymentIavl *iavl.Store) bo ctx.Logger().Error("fail to unmarshal stream record", "err", err.Error()) } else { flowPre = flowPre.Add(sr.NetflowRate) + //TODO: impact performance, remove it later + j, _ := json.Marshal(sr) + ctx.Logger().Debug("stream_record_previous", "stream record", j, "addr", parseAddressFromStreamRecordKey(kBz)) } } } @@ -196,3 +204,8 @@ func parseAmountFromValue(value []byte) math.Int { } return amount } + +func parseAddressFromStreamRecordKey(key []byte) string { + start := len(StreamRecordKeyPrefix) + return sdk.AccAddress(key[start:]).String() +} diff --git a/deployment/localup/localup.sh b/deployment/localup/localup.sh index 4c314d632..72fcbacaf 100644 --- a/deployment/localup/localup.sh +++ b/deployment/localup/localup.sh @@ -150,10 +150,10 @@ function generate_genesis() { sed -i -e "s/\"heartbeat_interval\": \"1000\"/\"heartbeat_interval\": \"100\"/g" ${workspace}/.local/validator${i}/config/genesis.json sed -i -e "s/\"attestation_inturn_interval\": \"120\"/\"attestation_inturn_interval\": \"10\"/g" ${workspace}/.local/validator${i}/config/genesis.json sed -i -e "s/\"discontinue_confirm_period\": \"604800\"/\"discontinue_confirm_period\": \"5\"/g" ${workspace}/.local/validator${i}/config/genesis.json - sed -i -e "s/\"discontinue_deletion_max\": \"10000\"/\"discontinue_deletion_max\": \"1\"/g" ${workspace}/.local/validator${i}/config/genesis.json - sed -i -e "s/\"voting_period\": \"30s\"/\"voting_period\": \"10s\"/g" ${workspace}/.local/validator${i}/config/genesis.json + sed -i -e "s/\"discontinue_deletion_max\": \"10000\"/\"discontinue_deletion_max\": \"2\"/g" ${workspace}/.local/validator${i}/config/genesis.json + sed -i -e "s/\"voting_period\": \"30s\"/\"voting_period\": \"5s\"/g" ${workspace}/.local/validator${i}/config/genesis.json #sed -i -e "s/\"community_tax\": \"0.020000000000000000\"/\"community_tax\": \"0\"/g" ${workspace}/.local/validator${i}/config/genesis.json - #sed -i -e "s/log_level = \"info\"/\log_level= \"debug\"/g" ${workspace}/.local/validator${i}/config/config.toml + sed -i -e "s/log_level = \"info\"/\log_level= \"debug\"/g" ${workspace}/.local/validator${i}/config/config.toml done # enable swagger API for validator0 diff --git a/e2e/core/basesuite.go b/e2e/core/basesuite.go index 94cb5180f..3214fb6be 100644 --- a/e2e/core/basesuite.go +++ b/e2e/core/basesuite.go @@ -155,7 +155,7 @@ func (s *BaseSuite) SendTxBlock(from keys.KeyManager, msg ...sdk.Msg) *sdk.TxRes }) s.Require().NoError(err) - s.T().Logf("block_height: %d, tx_hash: 0x%s", getTxRes.TxResponse.Height, response.TxResponse.TxHash) + s.T().Logf("block_height: %d, tx_hash: 0x%s, GasUsed: %v", getTxRes.TxResponse.Height, response.TxResponse.TxHash, response.TxResponse.GasUsed) return getTxRes.TxResponse } @@ -200,7 +200,7 @@ func (s *BaseSuite) SendTxBlockWithExpectErrorString(msg sdk.Msg, from keys.KeyM } s.Client.SetKeyManager(from) _, err := s.Client.BroadcastTx(context.Background(), []sdk.Msg{msg}, txOpt) - s.T().Logf("tx failed, err: %s, expect error string: %s", err, expectErrorString) + s.T().Logf("tx failed, err: %v, expect error string: %s", err, expectErrorString) s.Require().Error(err) s.Require().True(strings.Contains(err.Error(), expectErrorString)) } @@ -642,6 +642,15 @@ func (s *BaseSuite) PickStorageProvider() *StorageProvider { return nil } +func (s *BaseSuite) PickDifferentStorageProvider(spId uint32) *StorageProvider { + for _, sp := range s.StorageProviders { + if sp.Info.Id != spId { + return sp + } + } + return nil +} + func (s *BaseSuite) PickStorageProviderByBucketName(bucketName string) *StorageProvider { queryHeadBucketResponse, err := s.Client.HeadBucket(context.Background(), &storagetypes.QueryHeadBucketRequest{ BucketName: bucketName, diff --git a/e2e/tests/bridge_test.go b/e2e/tests/bridge_test.go index 8d48447b8..4d6df2237 100644 --- a/e2e/tests/bridge_test.go +++ b/e2e/tests/bridge_test.go @@ -8,7 +8,6 @@ import ( "time" sdkmath "cosmossdk.io/math" - gnfdtypes "github.com/bnb-chain/greenfield/sdk/types" "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/tx" @@ -22,6 +21,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/bnb-chain/greenfield/e2e/core" + gnfdtypes "github.com/bnb-chain/greenfield/sdk/types" types2 "github.com/bnb-chain/greenfield/sdk/types" bridgetypes "github.com/bnb-chain/greenfield/x/bridge/types" ) diff --git a/e2e/tests/payment_test.go b/e2e/tests/payment_test.go index 0a507689f..9d6e9fd61 100644 --- a/e2e/tests/payment_test.go +++ b/e2e/tests/payment_test.go @@ -31,7 +31,7 @@ import ( paymenttypes "github.com/bnb-chain/greenfield/x/payment/types" sptypes "github.com/bnb-chain/greenfield/x/sp/types" storagetypes "github.com/bnb-chain/greenfield/x/storage/types" - virtualgroupmoduletypes "github.com/bnb-chain/greenfield/x/virtualgroup/types" + virtualgrouptypes "github.com/bnb-chain/greenfield/x/virtualgroup/types" ) type StreamRecords struct { @@ -43,15 +43,20 @@ type StreamRecords struct { type PaymentTestSuite struct { core.BaseSuite + defaultParams paymenttypes.Params } func (s *PaymentTestSuite) SetupSuite() { s.BaseSuite.SetupSuite() + s.defaultParams = s.queryParams() + } -func (s *PaymentTestSuite) SetupTest() {} +func (s *PaymentTestSuite) SetupTest() { + s.RefreshGVGFamilies() +} -func (s *PaymentTestSuite) TestPaymentAccount() { +func (s *PaymentTestSuite) TestCreatePaymentAccount() { user := s.GenAndChargeAccounts(1, 100)[0] ctx := context.Background() // create a new payment account @@ -90,271 +95,30 @@ func (s *PaymentTestSuite) TestPaymentAccount() { s.Require().Equal(false, paymentAccount.PaymentAccount.Refundable) } -func (s *PaymentTestSuite) updateParams(params paymenttypes.Params) { - var err error - validator := s.Validator.GetAddr() - - ctx := context.Background() - - ts := time.Now().Unix() - queryParamsRequest := &paymenttypes.QueryParamsRequest{} - queryParamsResponse, err := s.Client.PaymentQueryClient.Params(ctx, queryParamsRequest) - s.Require().NoError(err) - - msgUpdateParams := &paymenttypes.MsgUpdateParams{ - Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), - Params: params, - } - - msgProposal, err := govtypesv1.NewMsgSubmitProposal( - []sdk.Msg{msgUpdateParams}, - sdk.Coins{sdk.NewCoin(s.BaseSuite.Config.Denom, types.NewIntFromInt64WithDecimal(100, types.DecimalBNB))}, - validator.String(), - "test", "test", "test", - ) - s.Require().NoError(err) - - txRes := s.SendTxBlock(s.Validator, msgProposal) - s.Require().Equal(txRes.Code, uint32(0)) - - // 3. query proposal and get proposal ID - var proposalId uint64 - for _, event := range txRes.Logs[0].Events { - if event.Type == "submit_proposal" { - for _, attr := range event.Attributes { - if attr.Key == "proposal_id" { - proposalId, err = strconv.ParseUint(attr.Value, 10, 0) - s.Require().NoError(err) - break - } - } - break - } - } - s.Require().True(proposalId != 0) - - queryProposal := &govtypesv1.QueryProposalRequest{ProposalId: proposalId} - _, err = s.Client.GovQueryClientV1.Proposal(ctx, queryProposal) - s.Require().NoError(err) - - // 4. submit MsgVote and wait the proposal exec - msgVote := govtypesv1.NewMsgVote(validator, proposalId, govtypesv1.OptionYes, "test") - txRes = s.SendTxBlock(s.Validator, msgVote) - s.Require().Equal(txRes.Code, uint32(0)) - - queryVoteParamsReq := govtypesv1.QueryParamsRequest{ParamsType: "voting"} - queryVoteParamsResp, err := s.Client.GovQueryClientV1.Params(ctx, &queryVoteParamsReq) - s.Require().NoError(err) - - // 5. wait a voting period and confirm that the proposal success. - s.T().Logf("voting period %s", *queryVoteParamsResp.Params.VotingPeriod) - time.Sleep(*queryVoteParamsResp.Params.VotingPeriod) - time.Sleep(1 * time.Second) - proposalRes, err := s.Client.GovQueryClientV1.Proposal(ctx, queryProposal) - s.Require().NoError(err) - s.Require().Equal(proposalRes.Proposal.Status, govtypesv1.ProposalStatus_PROPOSAL_STATUS_PASSED) - - queryParamsByTimestampRequest := &paymenttypes.QueryParamsByTimestampRequest{Timestamp: ts} - queryParamsByTimestampResponse, err := s.Client.PaymentQueryClient.ParamsByTimestamp(ctx, queryParamsByTimestampRequest) - s.Require().NoError(err) - s.Require().Equal(queryParamsResponse.Params.VersionedParams.ReserveTime, - queryParamsByTimestampResponse.Params.VersionedParams.ReserveTime) - s.T().Logf("new params: %s", params.String()) -} - -func (s *PaymentTestSuite) createBucketAndObject(sp *core.StorageProvider) (keys.KeyManager, string, string, storagetypes.Uint, [][]byte) { - var err error - - gvg, found := sp.GetFirstGlobalVirtualGroup() - s.Require().True(found) - - // CreateBucket - user := s.GenAndChargeAccounts(1, 1000000)[0] - bucketName := "ch" + storagetestutils.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) - - // CreateObject - objectName := storagetestutils.GenRandomObjectName() - // create test buffer - var buffer bytes.Buffer - line := `1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,123` - // 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(user, msgCreateObject) - - // HeadObject - queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ - BucketName: bucketName, - ObjectName: objectName, - } - queryHeadObjectResponse, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest) - s.Require().NoError(err) - s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectName, objectName) - s.Require().Equal(queryHeadObjectResponse.ObjectInfo.BucketName, bucketName) - - return user, bucketName, objectName, queryHeadObjectResponse.ObjectInfo.Id, expectChecksum -} - -func (s *PaymentTestSuite) createBucket() (keys.KeyManager, string) { - var err error - sp := s.BaseSuite.PickStorageProvider() - gvg, found := sp.GetFirstGlobalVirtualGroup() - s.Require().True(found) - - // CreateBucket - user := s.GenAndChargeAccounts(1, 1000000)[0] - bucketName := "ch" + storagetestutils.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) - - return user, bucketName -} - -func (s *PaymentTestSuite) createObject(user keys.KeyManager, bucketName string) (keys.KeyManager, string, string, storagetypes.Uint, [][]byte) { - var err error - - sp := s.BaseSuite.PickStorageProviderByBucketName(bucketName) - - // CreateObject - objectName := storagetestutils.GenRandomObjectName() - // create test buffer - var buffer bytes.Buffer - line := `1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, - 1234567890,1234567890,1234567890,123` - // 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(user, msgCreateObject) - - // HeadObject - queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ - BucketName: bucketName, - ObjectName: objectName, - } - queryHeadObjectResponse, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest) - s.Require().NoError(err) - s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectName, objectName) - s.Require().Equal(queryHeadObjectResponse.ObjectInfo.BucketName, bucketName) - - return user, bucketName, objectName, queryHeadObjectResponse.ObjectInfo.Id, expectChecksum -} - -func (s *PaymentTestSuite) sealObject(bucketName, objectName string, objectId storagetypes.Uint, checksums [][]byte) { - sp := s.BaseSuite.PickStorageProviderByBucketName(bucketName) - gvg, found := sp.GetFirstGlobalVirtualGroup() - s.Require().True(found) - s.T().Log("GVG info: ", gvg.String()) - - // 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, objectId, storagetypes.GenerateHash(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) - - queryHeadObjectRequest2 := storagetypes.QueryHeadObjectRequest{ - BucketName: bucketName, - ObjectName: objectName, - } - queryHeadObjectResponse2, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest2) - s.Require().NoError(err) - s.Require().Equal(queryHeadObjectResponse2.ObjectInfo.ObjectName, objectName) - s.Require().Equal(queryHeadObjectResponse2.ObjectInfo.BucketName, bucketName) - s.Require().Equal(queryHeadObjectResponse2.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_SEALED) -} - // TestVersionedParams_SealAfterReserveTimeChange will cover the following case: // create an object, increase the reserve time, seal the object without error. func (s *PaymentTestSuite) TestVersionedParams_SealObjectAfterReserveTimeChange() { + defer s.revertParams() + ctx := context.Background() - sp := s.BaseSuite.PickStorageProvider() - queryParamsRequest := paymenttypes.QueryParamsRequest{} - queryParamsResponse, err := s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) // create bucket, create object user, bucketName, objectName, objectId, checksums := s.createBucketAndObject(sp) // update params - params := queryParamsResponse.GetParams() + params := s.queryParams() oldReserveTime := params.VersionedParams.ReserveTime oldValidatorTaxRate := params.VersionedParams.ValidatorTaxRate - s.T().Logf("params, ReserveTime: %d, ValidatorTaxRate: %s", oldReserveTime, oldValidatorTaxRate) params.VersionedParams.ReserveTime = oldReserveTime * 2 params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate.MulInt64(2) - s.updateParams(params) - queryParamsResponse, err = s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) - params = queryParamsResponse.GetParams() - s.T().Logf("params, ReserveTime: %d, ValidatorTaxRate: %s", params.VersionedParams.ReserveTime, params.VersionedParams.ValidatorTaxRate) // seal object - s.sealObject(bucketName, objectName, objectId, checksums) + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ BucketName: bucketName, @@ -371,23 +135,19 @@ func (s *PaymentTestSuite) TestVersionedParams_SealObjectAfterReserveTimeChange( // delete bucket msgDeleteBucket := storagetypes.NewMsgDeleteBucket(user.GetAddr(), bucketName) s.SendTxBlock(user, msgDeleteBucket) - - // revert params - params.VersionedParams.ReserveTime = oldReserveTime - params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate - s.updateParams(params) } // TestVersionedParams_DeleteAfterValidatorTaxRateChange will cover the following case: // create a bucket with non-zero read quota, change the validator tax rate, delete the bucket. // The rate of the validator tax address should be correct. func (s *PaymentTestSuite) TestVersionedParams_DeleteBucketAfterValidatorTaxRateChange() { + defer s.revertParams() + ctx := context.Background() - queryParamsRequest := paymenttypes.QueryParamsRequest{} - queryParamsResponse, err := s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) - sp := s.BaseSuite.PickStorageProvider() validatorTaxPoolRate := sdk.ZeroInt() queryStreamRequest := paymenttypes.QueryGetStreamRecordRequest{Account: paymenttypes.ValidatorTaxPoolAddress.String()} queryStreamResponse, err := s.Client.PaymentQueryClient.StreamRecord(ctx, &queryStreamRequest) @@ -403,22 +163,19 @@ func (s *PaymentTestSuite) TestVersionedParams_DeleteBucketAfterValidatorTaxRate user, bucketName, objectName, objectId, checksums := s.createBucketAndObject(sp) // seal object - s.sealObject(bucketName, objectName, objectId, checksums) + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) // update params - params := queryParamsResponse.GetParams() + params := s.queryParams() oldReserveTime := params.VersionedParams.ReserveTime + oldForceSettleTime := params.ForcedSettleTime oldValidatorTaxRate := params.VersionedParams.ValidatorTaxRate s.T().Logf("params, ReserveTime: %d, ValidatorTaxRate: %s", oldReserveTime, oldValidatorTaxRate) params.VersionedParams.ReserveTime = oldReserveTime / 2 + params.ForcedSettleTime = oldForceSettleTime / 2 params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate.MulInt64(2) - s.updateParams(params) - queryParamsResponse, err = s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) - params = queryParamsResponse.GetParams() - s.T().Logf("params, ReserveTime: %d, ValidatorTaxRate: %s", params.VersionedParams.ReserveTime, params.VersionedParams.ValidatorTaxRate) // delete object msgDeleteObject := storagetypes.NewMsgDeleteObject(user.GetAddr(), bucketName, objectName) @@ -431,27 +188,23 @@ func (s *PaymentTestSuite) TestVersionedParams_DeleteBucketAfterValidatorTaxRate queryStreamResponse, err = s.Client.PaymentQueryClient.StreamRecord(ctx, &queryStreamRequest) s.Require().NoError(err) s.Require().Equal(validatorTaxPoolRate, queryStreamResponse.StreamRecord.NetflowRate) - - // revert params - params.VersionedParams.ReserveTime = oldReserveTime - params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate - s.updateParams(params) } // TestVersionedParams_DeleteObjectAfterReserveTimeChange will cover the following case: // create an object, change the reserve time, the object can be force deleted even the object's own has no enough balance. func (s *PaymentTestSuite) TestVersionedParams_DeleteObjectAfterReserveTimeChange() { + defer s.revertParams() + ctx := context.Background() - sp := s.BaseSuite.PickStorageProvider() - queryParamsRequest := paymenttypes.QueryParamsRequest{} - queryParamsResponse, err := s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) // create bucket, create object user, bucketName, objectName, objectId, checksums := s.createBucketAndObject(sp) // seal object - s.sealObject(bucketName, objectName, objectId, checksums) + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) // for payment time.Sleep(2 * time.Second) @@ -478,19 +231,13 @@ func (s *PaymentTestSuite) TestVersionedParams_DeleteObjectAfterReserveTimeChang s.Require().Equal(int64(0), queryBalanceResponse.Balance.Amount.Int64()) // update params - params := queryParamsResponse.GetParams() + params := s.queryParams() oldReserveTime := params.VersionedParams.ReserveTime oldValidatorTaxRate := params.VersionedParams.ValidatorTaxRate - s.T().Logf("params, ReserveTime: %d, ValidatorTaxRate: %s", oldReserveTime, oldValidatorTaxRate) params.VersionedParams.ReserveTime = oldReserveTime * 2 params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate.MulInt64(2) - s.updateParams(params) - queryParamsResponse, err = s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) - params = queryParamsResponse.GetParams() - s.T().Logf("params, ReserveTime: %d, ValidatorTaxRate: %s", params.VersionedParams.ReserveTime, params.VersionedParams.ValidatorTaxRate) queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ BucketName: bucketName, @@ -520,26 +267,105 @@ func (s *PaymentTestSuite) TestVersionedParams_DeleteObjectAfterReserveTimeChang _, err = s.Client.HeadObject(ctx, &queryHeadObjectRequest) s.Require().ErrorContains(err, "No such object") - - // revert params - params.VersionedParams.ReserveTime = oldReserveTime - params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate - s.updateParams(params) } -func (s *PaymentTestSuite) TestDepositAndResume_InOneBlock() { +func (s *PaymentTestSuite) TestDeposit_ActiveAccount() { ctx := context.Background() - sp := s.BaseSuite.PickStorageProvider() + sp := s.PickStorageProvider() gvg, found := sp.GetFirstGlobalVirtualGroup() s.Require().True(found) user := s.GenAndChargeAccounts(1, 1000000)[0] userAddr := user.GetAddr().String() var err error - params, err := s.Client.PaymentQueryClient.Params(ctx, &paymenttypes.QueryParamsRequest{}) - s.T().Logf("params %s, err: %v", params, err) + params := s.queryParams() + reserveTime := params.VersionedParams.ReserveTime + queryGetSpStoragePriceByTimeResp, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + }) + s.T().Logf("queryGetSpStoragePriceByTimeResp %s, err: %v", queryGetSpStoragePriceByTimeResp, err) + s.Require().NoError(err) + + bucketChargedReadQuota := uint64(1000) + readPrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice + totalUserRate := readPrice.MulInt(sdkmath.NewIntFromUint64(bucketChargedReadQuota)).TruncateInt() + taxRateParam := params.VersionedParams.ValidatorTaxRate + taxStreamRate := taxRateParam.MulInt(totalUserRate).TruncateInt() + expectedRate := totalUserRate.Add(taxStreamRate) + paymentAccountBNBNeeded := expectedRate.Mul(sdkmath.NewIntFromUint64(reserveTime)) + s.T().Log("paymentAccountBNBNeeded", paymentAccountBNBNeeded.String()) + + // create payment account and deposit + msgCreatePaymentAccount := &paymenttypes.MsgCreatePaymentAccount{ + Creator: userAddr, + } + _ = s.SendTxBlock(user, msgCreatePaymentAccount) + paymentAccountsReq := &paymenttypes.QueryGetPaymentAccountsByOwnerRequest{Owner: userAddr} + paymentAccounts, err := s.Client.PaymentQueryClient.GetPaymentAccountsByOwner(ctx, paymentAccountsReq) + s.Require().NoError(err) + s.T().Logf("paymentAccounts %s", core.YamlString(paymentAccounts)) + paymentAddr := paymentAccounts.PaymentAccounts[0] + s.Require().Lenf(paymentAccounts.PaymentAccounts, 1, "paymentAccounts %s", core.YamlString(paymentAccounts)) + + // deposit BNB needed + msgDeposit := &paymenttypes.MsgDeposit{ + Creator: user.GetAddr().String(), + To: paymentAddr, + Amount: paymentAccountBNBNeeded.MulRaw(2), // deposit more than needed + } + _ = s.SendTxBlock(user, msgDeposit) + + // create bucket + bucketName := storagetestutils.GenRandomBucketName() + msgCreateBucket := storagetypes.NewMsgCreateBucket( + user.GetAddr(), bucketName, storagetypes.VISIBILITY_TYPE_PUBLIC_READ, sp.OperatorKey.GetAddr(), + sdk.MustAccAddressFromHex(paymentAddr), math.MaxUint, nil, bucketChargedReadQuota) + msgCreateBucket.PrimarySpApproval.GlobalVirtualGroupFamilyId = gvg.FamilyId + msgCreateBucket.PrimarySpApproval.Sig, err = sp.ApprovalKey.Sign(msgCreateBucket.GetApprovalBytes()) s.Require().NoError(err) - reserveTime := params.Params.VersionedParams.ReserveTime + s.SendTxBlock(user, msgCreateBucket) + + // check payment account stream record + paymentAccountStreamRecord := s.getStreamRecord(paymentAddr) + s.T().Logf("paymentAccountStreamRecord %s", core.YamlString(paymentAccountStreamRecord)) + s.Require().Equal(expectedRate.String(), paymentAccountStreamRecord.NetflowRate.Neg().String()) + s.Require().Equal(paymentAccountStreamRecord.BufferBalance.String(), paymentAccountBNBNeeded.String()) + s.Require().Equal(paymentAccountStreamRecord.StaticBalance.String(), paymentAccountBNBNeeded.String()) + + time.Sleep(5 * time.Second) + + // deposit + msgDeposit = &paymenttypes.MsgDeposit{ + Creator: userAddr, + To: paymentAddr, + Amount: paymentAccountBNBNeeded, + } + _ = s.SendTxBlock(user, msgDeposit) + + // check payment account stream record + paymentAccountStreamRecordAfter := s.getStreamRecord(paymentAddr) + s.T().Logf("paymentAccountStreamRecordAfter %s", core.YamlString(paymentAccountStreamRecordAfter)) + s.Require().Equal(paymentAccountStreamRecordAfter.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) + + settledTime := paymentAccountStreamRecordAfter.CrudTimestamp - paymentAccountStreamRecord.CrudTimestamp + settledBalance := expectedRate.MulRaw(settledTime) + paymentBalanceChange := paymentAccountStreamRecordAfter.StaticBalance.Sub(paymentAccountStreamRecord.StaticBalance). + Add(paymentAccountStreamRecordAfter.BufferBalance.Sub(paymentAccountStreamRecord.BufferBalance)) + s.Require().Equal(settledBalance.Add(paymentBalanceChange).Int64(), paymentAccountBNBNeeded.Int64()) + s.Require().Equal(paymentAccountBNBNeeded.MulRaw(3), settledBalance.Add(paymentAccountStreamRecordAfter.StaticBalance.Add(paymentAccountStreamRecordAfter.BufferBalance))) +} + +func (s *PaymentTestSuite) TestDeposit_ResumeInOneBlock() { + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + user := s.GenAndChargeAccounts(1, 1000000)[0] + userAddr := user.GetAddr().String() + var err error + + params := s.queryParams() + reserveTime := params.VersionedParams.ReserveTime queryGetSpStoragePriceByTimeResp, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ SpAddr: sp.OperatorKey.GetAddr().String(), }) @@ -549,7 +375,7 @@ func (s *PaymentTestSuite) TestDepositAndResume_InOneBlock() { bucketChargedReadQuota := uint64(1000) readPrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice totalUserRate := readPrice.MulInt(sdkmath.NewIntFromUint64(bucketChargedReadQuota)).TruncateInt() - taxRateParam := params.Params.VersionedParams.ValidatorTaxRate + taxRateParam := params.VersionedParams.ValidatorTaxRate taxStreamRate := taxRateParam.MulInt(totalUserRate).TruncateInt() expectedRate := totalUserRate.Add(taxStreamRate) paymentAccountBNBNeeded := expectedRate.Mul(sdkmath.NewIntFromUint64(reserveTime)) @@ -585,7 +411,7 @@ func (s *PaymentTestSuite) TestDepositAndResume_InOneBlock() { s.SendTxBlock(user, msgCreateBucket) // check payment account stream record - paymentAccountStreamRecord := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecord := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecord %s", core.YamlString(paymentAccountStreamRecord)) s.Require().Equal(expectedRate.String(), paymentAccountStreamRecord.NetflowRate.Neg().String()) s.Require().Equal(paymentAccountStreamRecord.BufferBalance.String(), paymentAccountBNBNeeded.String()) @@ -608,7 +434,7 @@ func (s *PaymentTestSuite) TestDepositAndResume_InOneBlock() { } } // check auto settle - paymentStreamRecordAfterAutoSettle := s.GetStreamRecord(paymentAddr) + paymentStreamRecordAfterAutoSettle := s.getStreamRecord(paymentAddr) s.T().Logf("paymentStreamRecordAfterAutoSettle %s", core.YamlString(paymentStreamRecordAfterAutoSettle)) s.Require().NotEqual(paymentStreamRecordAfterAutoSettle.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) @@ -622,7 +448,7 @@ func (s *PaymentTestSuite) TestDepositAndResume_InOneBlock() { _ = s.SendTxBlock(user, msgDeposit1) // check payment account stream record - paymentAccountStreamRecordAfterDeposit1 := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecordAfterDeposit1 := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecordAfterDeposit1 %s", core.YamlString(paymentAccountStreamRecordAfterDeposit1)) s.Require().NotEqual(paymentAccountStreamRecordAfterDeposit1.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) @@ -635,30 +461,22 @@ func (s *PaymentTestSuite) TestDepositAndResume_InOneBlock() { } s.SendTxBlock(user, msgDeposit2) // check payment account stream record - paymentAccountStreamRecordAfterDeposit2 := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecordAfterDeposit2 := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecordAfterDeposit2 %s", core.YamlString(paymentAccountStreamRecordAfterDeposit2)) s.Require().Equal(paymentAccountStreamRecordAfterDeposit2.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) s.Require().Equal(paymentAccountStreamRecordAfterDeposit2.StaticBalance.Add(paymentAccountStreamRecordAfterDeposit2.BufferBalance).String(), paymentAccountStreamRecordAfterDeposit1.StaticBalance.Add(depositAmount2).String()) } -func (s *PaymentTestSuite) TestDepositAndResume_InBlocks() { +func (s *PaymentTestSuite) TestDeposit_ResumeInBlocks() { + defer s.revertParams() + ctx := context.Background() // update params - queryParamsRequest := paymenttypes.QueryParamsRequest{} - queryParamsResponse, err := s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) - params := queryParamsResponse.GetParams() - oldMaxAutoResumeFlowCount := params.MaxAutoResumeFlowCount - s.T().Logf("params, MaxAutoResumeFlowCount: %d", oldMaxAutoResumeFlowCount) - + params := s.queryParams() params.MaxAutoResumeFlowCount = 1 // update to 1 s.updateParams(params) - queryParamsResponse, err = s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) - params = queryParamsResponse.GetParams() - s.T().Logf("params: %s", params.String()) - sp := s.BaseSuite.PickStorageProvider() + sp := s.PickStorageProvider() gvg, found := sp.GetFirstGlobalVirtualGroup() s.Require().True(found) user := s.GenAndChargeAccounts(1, 1000000)[0] @@ -710,7 +528,7 @@ func (s *PaymentTestSuite) TestDepositAndResume_InBlocks() { s.SendTxBlock(user, msgCreateBucket) // check payment account stream record - paymentAccountStreamRecord := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecord := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecord %s", core.YamlString(paymentAccountStreamRecord)) s.Require().Equal(expectedRate.String(), paymentAccountStreamRecord.NetflowRate.Neg().String()) s.Require().Equal(paymentAccountStreamRecord.BufferBalance.String(), paymentAccountBNBNeeded.String()) @@ -733,7 +551,7 @@ func (s *PaymentTestSuite) TestDepositAndResume_InBlocks() { } } // check auto settle - paymentStreamRecordAfterAutoSettle := s.GetStreamRecord(paymentAddr) + paymentStreamRecordAfterAutoSettle := s.getStreamRecord(paymentAddr) s.T().Logf("paymentStreamRecordAfterAutoSettle %s", core.YamlString(paymentStreamRecordAfterAutoSettle)) s.Require().NotEqual(paymentStreamRecordAfterAutoSettle.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) @@ -752,7 +570,7 @@ func (s *PaymentTestSuite) TestDepositAndResume_InBlocks() { s.SendTxWithTxOpt(msgDeposit, user, txOpt) // check payment account stream record - paymentAccountStreamRecordAfterDeposit := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecordAfterDeposit := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecordAfterDeposit %s", core.YamlString(paymentAccountStreamRecordAfterDeposit)) s.Require().NotEqual(paymentAccountStreamRecordAfterDeposit.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) @@ -761,8 +579,18 @@ func (s *PaymentTestSuite) TestDepositAndResume_InBlocks() { latestBlock, err := s.TmClient.TmClient.Block(ctx, nil) s.Require().NoError(err) - paymentAccountStreamRecordAfterDeposit = s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecordAfterDeposit = s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecordAfterDeposit %s at %d", core.YamlString(paymentAccountStreamRecordAfterDeposit), latestBlock.Block.Height) + if paymentAccountStreamRecordAfterDeposit.Status == paymenttypes.STREAM_ACCOUNT_STATUS_FROZEN && + !paymentAccountStreamRecordAfterDeposit.NetflowRate.IsZero() { + s.T().Log("trying to deposit, which will error") + msgDeposit = &paymenttypes.MsgDeposit{ + Creator: user.GetAddr().String(), + To: paymentAddr, + Amount: paymentAccountBNBNeeded, + } + s.SendTxBlockWithExpectErrorString(msgDeposit, user, "resuming") + } if paymentAccountStreamRecordAfterDeposit.Status == paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE { break } @@ -772,34 +600,26 @@ func (s *PaymentTestSuite) TestDepositAndResume_InBlocks() { s.T().Fatalf("wait for resume time timeout") } } - - // revert params - params.MaxAutoResumeFlowCount = oldMaxAutoResumeFlowCount - s.updateParams(params) } func (s *PaymentTestSuite) TestAutoSettle_InOneBlock() { ctx := context.Background() - sp := s.BaseSuite.PickStorageProvider() + sp := s.PickStorageProvider() gvg, found := sp.GetFirstGlobalVirtualGroup() s.Require().True(found) - s.T().Log(gvg.String()) user := s.GenAndChargeAccounts(1, 1000000)[0] userAddr := user.GetAddr().String() var err error - queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgroupmoduletypes.QueryGlobalVirtualGroupFamilyRequest{ + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ FamilyId: gvg.FamilyId, }) s.Require().NoError(err) family := queryFamilyResponse.GlobalVirtualGroupFamily bucketChargedReadQuota := uint64(1000) - paymentParams, err := s.Client.PaymentQueryClient.Params(ctx, &paymenttypes.QueryParamsRequest{}) - s.T().Logf("paymentParams %s, err: %v", paymentParams, err) - s.Require().NoError(err) - reserveTime := paymentParams.Params.VersionedParams.ReserveTime - forcedSettleTime := paymentParams.Params.ForcedSettleTime + params := s.queryParams() + reserveTime := params.VersionedParams.ReserveTime queryGetSpStoragePriceByTimeResp, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ SpAddr: sp.OperatorKey.GetAddr().String(), }) @@ -807,7 +627,7 @@ func (s *PaymentTestSuite) TestAutoSettle_InOneBlock() { s.Require().NoError(err) readPrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice totalUserRate := readPrice.MulInt(sdkmath.NewIntFromUint64(bucketChargedReadQuota)).TruncateInt() - taxRateParam := paymentParams.Params.VersionedParams.ValidatorTaxRate + taxRateParam := params.VersionedParams.ValidatorTaxRate taxStreamRate := taxRateParam.MulInt(totalUserRate).TruncateInt() expectedRate := totalUserRate.Add(taxStreamRate) paymentAccountBNBNeeded := expectedRate.Mul(sdkmath.NewIntFromUint64(reserveTime)) @@ -840,11 +660,13 @@ func (s *PaymentTestSuite) TestAutoSettle_InOneBlock() { s.Require().NoError(err) s.SendTxBlock(user, msgCreateBucket) // check payment account stream record - paymentAccountStreamRecord := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecord := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecord %s", core.YamlString(paymentAccountStreamRecord)) s.Require().Equal(expectedRate.String(), paymentAccountStreamRecord.NetflowRate.Neg().String()) s.Require().Equal(paymentAccountStreamRecord.BufferBalance.String(), paymentAccountBNBNeeded.String()) s.Require().Equal(paymentAccountStreamRecord.StaticBalance.String(), sdkmath.ZeroInt().String()) + govStreamRecord := s.getStreamRecord(paymenttypes.GovernanceAddress.String()) + s.T().Logf("govStreamRecord %s", core.YamlString(govStreamRecord)) // increase bucket charged read quota is not allowed since the balance is not enough msgUpdateBucketInfo := &storagetypes.MsgUpdateBucketInfo{ @@ -853,25 +675,7 @@ func (s *PaymentTestSuite) TestAutoSettle_InOneBlock() { ChargedReadQuota: &common.UInt64Value{Value: bucketChargedReadQuota + 1}, Visibility: storagetypes.VISIBILITY_TYPE_PUBLIC_READ, } - _, err = s.SendTxBlockWithoutCheck(msgUpdateBucketInfo, user) - s.Require().ErrorContains(err, "balance not enough, lack of") - - // create bucket from user - msgCreateBucket.BucketName = storagetestutils.GenRandomBucketName() - msgCreateBucket.PaymentAddress = "" - msgCreateBucket.PrimarySpApproval.GlobalVirtualGroupFamilyId = gvg.FamilyId - msgCreateBucket.PrimarySpApproval.Sig, err = sp.ApprovalKey.Sign(msgCreateBucket.GetApprovalBytes()) - s.Require().NoError(err) - s.SendTxBlock(user, msgCreateBucket) - - // check user stream record - userStreamRecord := s.GetStreamRecord(userAddr) - s.T().Logf("userStreamRecord %s", core.YamlString(userStreamRecord)) - s.Require().Equal(userStreamRecord.SettleTimestamp, userStreamRecord.CrudTimestamp+int64(reserveTime-forcedSettleTime)) - familyStreamRecord := s.GetStreamRecord(family.VirtualPaymentAddress) - s.T().Logf("familyStreamRecord %s", core.YamlString(familyStreamRecord)) - govStreamRecord := s.GetStreamRecord(paymenttypes.GovernanceAddress.String()) - s.T().Logf("govStreamRecord %s", core.YamlString(govStreamRecord)) + s.SendTxBlockWithExpectErrorString(msgUpdateBucketInfo, user, "balance not enough, lack of") // wait until settle time retryCount := 0 @@ -879,8 +683,8 @@ func (s *PaymentTestSuite) TestAutoSettle_InOneBlock() { latestBlock, err := s.TmClient.TmClient.Block(ctx, nil) s.Require().NoError(err) currentTimestamp := latestBlock.Block.Time.Unix() - s.T().Logf("currentTimestamp %d, userStreamRecord.SettleTimestamp %d", currentTimestamp, userStreamRecord.SettleTimestamp) - if currentTimestamp > userStreamRecord.SettleTimestamp { + s.T().Logf("currentTimestamp %d, userStreamRecord.SettleTimestamp %d", currentTimestamp, paymentAccountStreamRecord.SettleTimestamp) + if currentTimestamp > paymentAccountStreamRecord.SettleTimestamp { break } time.Sleep(time.Second) @@ -890,26 +694,23 @@ func (s *PaymentTestSuite) TestAutoSettle_InOneBlock() { } } // check auto settle - userStreamRecordAfterAutoSettle := s.GetStreamRecord(userAddr) + userStreamRecordAfterAutoSettle := s.getStreamRecord(userAddr) s.T().Logf("userStreamRecordAfterAutoSettle %s", core.YamlString(userStreamRecordAfterAutoSettle)) - familyStreamRecordAfterAutoSettle := s.GetStreamRecord(family.VirtualPaymentAddress) + familyStreamRecordAfterAutoSettle := s.getStreamRecord(family.VirtualPaymentAddress) s.T().Logf("familyStreamRecordAfterAutoSettle %s", core.YamlString(familyStreamRecordAfterAutoSettle)) - paymentAccountStreamRecordAfterAutoSettle := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecordAfterAutoSettle := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecordAfterAutoSettle %s", core.YamlString(paymentAccountStreamRecordAfterAutoSettle)) // payment account become frozen s.Require().NotEqual(paymentAccountStreamRecordAfterAutoSettle.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) s.Require().Equal(familyStreamRecordAfterAutoSettle.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) s.Require().Equal(userStreamRecordAfterAutoSettle.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) - // user settle time become refreshed - s.Require().NotEqual(userStreamRecordAfterAutoSettle.SettleTimestamp, userStreamRecord.SettleTimestamp) - s.Require().Equal(userStreamRecordAfterAutoSettle.SettleTimestamp, userStreamRecordAfterAutoSettle.CrudTimestamp+int64(reserveTime-forcedSettleTime)) - // gov stream record balance increase - govStreamRecordAfterSettle := s.GetStreamRecord(paymenttypes.GovernanceAddress.String()) + + govStreamRecordAfterSettle := s.getStreamRecord(paymenttypes.GovernanceAddress.String()) s.T().Logf("govStreamRecordAfterSettle %s", core.YamlString(govStreamRecordAfterSettle)) s.Require().NotEqual(govStreamRecordAfterSettle.StaticBalance.String(), govStreamRecord.StaticBalance.String()) govStreamRecordStaticBalanceDelta := govStreamRecordAfterSettle.StaticBalance.Sub(govStreamRecord.StaticBalance) - expectedGovBalanceDelta := userStreamRecord.NetflowRate.Neg().MulRaw(userStreamRecordAfterAutoSettle.CrudTimestamp - userStreamRecord.CrudTimestamp) - s.Require().Equal(expectedGovBalanceDelta.String(), govStreamRecordStaticBalanceDelta.String()) + expectedGovBalanceDelta := paymentAccountStreamRecord.NetflowRate.Neg().MulRaw(paymentAccountStreamRecordAfterAutoSettle.CrudTimestamp - paymentAccountStreamRecord.CrudTimestamp) + s.Require().True(govStreamRecordStaticBalanceDelta.Int64() >= expectedGovBalanceDelta.Int64()) // deposit, balance not enough to resume depositAmount1 := sdk.NewInt(1) @@ -920,7 +721,7 @@ func (s *PaymentTestSuite) TestAutoSettle_InOneBlock() { } _ = s.SendTxBlock(user, msgDeposit1) // check payment account stream record - paymentAccountStreamRecordAfterDeposit1 := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecordAfterDeposit1 := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecordAfterDeposit1 %s", core.YamlString(paymentAccountStreamRecordAfterDeposit1)) s.Require().NotEqual(paymentAccountStreamRecordAfterDeposit1.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) s.Require().Equal(paymentAccountStreamRecordAfterDeposit1.StaticBalance.String(), paymentAccountStreamRecordAfterAutoSettle.StaticBalance.Add(depositAmount1).String()) @@ -934,30 +735,22 @@ func (s *PaymentTestSuite) TestAutoSettle_InOneBlock() { } s.SendTxBlock(user, msgDeposit2) // check payment account stream record - paymentAccountStreamRecordAfterDeposit2 := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecordAfterDeposit2 := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecordAfterDeposit2 %s", core.YamlString(paymentAccountStreamRecordAfterDeposit2)) s.Require().Equal(paymentAccountStreamRecordAfterDeposit2.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) s.Require().Equal(paymentAccountStreamRecordAfterDeposit2.StaticBalance.Add(paymentAccountStreamRecordAfterDeposit2.BufferBalance).String(), paymentAccountStreamRecordAfterDeposit1.StaticBalance.Add(depositAmount2).String()) } func (s *PaymentTestSuite) TestAutoSettle_InBlocks() { + defer s.revertParams() + ctx := context.Background() // update params - queryParamsRequest := paymenttypes.QueryParamsRequest{} - queryParamsResponse, err := s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) - params := queryParamsResponse.GetParams() - oldMaxAutoSettleFlowCount := params.MaxAutoSettleFlowCount - s.T().Logf("params, MaxAutoSettleFlowCount: %d", oldMaxAutoSettleFlowCount) - + params := s.queryParams() params.MaxAutoSettleFlowCount = 2 // update to 2 s.updateParams(params) - queryParamsResponse, err = s.Client.PaymentQueryClient.Params(ctx, &queryParamsRequest) - s.Require().NoError(err) - params = queryParamsResponse.GetParams() - s.T().Logf("params: %s", params.String()) - sp := s.BaseSuite.PickStorageProvider() + sp := s.PickStorageProvider() gvg, found := sp.GetFirstGlobalVirtualGroup() s.Require().True(found) user := s.GenAndChargeAccounts(1, 1000000)[0] @@ -1009,7 +802,7 @@ func (s *PaymentTestSuite) TestAutoSettle_InBlocks() { s.SendTxBlock(user, msgCreateBucket) // check payment account stream record - paymentAccountStreamRecord := s.GetStreamRecord(paymentAddr) + paymentAccountStreamRecord := s.getStreamRecord(paymentAddr) s.T().Logf("paymentAccountStreamRecord %s", core.YamlString(paymentAccountStreamRecord)) s.Require().Equal(expectedRate.String(), paymentAccountStreamRecord.NetflowRate.Neg().String()) s.Require().Equal(paymentAccountStreamRecord.BufferBalance.String(), paymentAccountBNBNeeded.String()) @@ -1025,7 +818,7 @@ func (s *PaymentTestSuite) TestAutoSettle_InBlocks() { if currentTimestamp > paymentAccountStreamRecord.SettleTimestamp { break } - time.Sleep(2 * time.Second) + time.Sleep(time.Second) retryCount++ if retryCount > 60 { s.T().Fatalf("wait for settle time timeout") @@ -1033,33 +826,113 @@ func (s *PaymentTestSuite) TestAutoSettle_InBlocks() { } // check auto settle for { - paymentStreamRecordAfterAutoSettle := s.GetStreamRecord(paymentAddr) + paymentStreamRecordAfterAutoSettle := s.getStreamRecord(paymentAddr) s.T().Logf("paymentStreamRecordAfterAutoSettle %s", core.YamlString(paymentStreamRecordAfterAutoSettle)) if paymentStreamRecordAfterAutoSettle.NetflowRate.IsZero() { break } - time.Sleep(1000 * time.Millisecond) + time.Sleep(500 * time.Millisecond) retryCount++ if retryCount > 60 { s.T().Fatalf("wait for settle time timeout") } } - paymentStreamRecordAfterAutoSettle := s.GetStreamRecord(paymentAddr) + paymentStreamRecordAfterAutoSettle := s.getStreamRecord(paymentAddr) s.T().Logf("paymentStreamRecordAfterAutoSettle %s", core.YamlString(paymentStreamRecordAfterAutoSettle)) s.Require().NotEqual(paymentStreamRecordAfterAutoSettle.Status, paymenttypes.STREAM_ACCOUNT_STATUS_ACTIVE) +} - // revert params - params.MaxAutoSettleFlowCount = oldMaxAutoSettleFlowCount - s.updateParams(params) +func (s *PaymentTestSuite) TestWithdraw() { + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + user := s.GenAndChargeAccounts(1, 1000000)[0] + userAddr := user.GetAddr().String() + var err error + + params := s.queryParams() + reserveTime := params.VersionedParams.ReserveTime + queryGetSpStoragePriceByTimeResp, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + }) + s.T().Logf("queryGetSpStoragePriceByTimeResp %s, err: %v", queryGetSpStoragePriceByTimeResp, err) + s.Require().NoError(err) + + bucketChargedReadQuota := uint64(100000) + readPrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice + totalUserRate := readPrice.MulInt(sdkmath.NewIntFromUint64(bucketChargedReadQuota)).TruncateInt() + taxRateParam := params.VersionedParams.ValidatorTaxRate + taxStreamRate := taxRateParam.MulInt(totalUserRate).TruncateInt() + expectedRate := totalUserRate.Add(taxStreamRate) + paymentAccountBNBNeeded := expectedRate.Mul(sdkmath.NewIntFromUint64(reserveTime)) + s.T().Log("paymentAccountBNBNeeded", paymentAccountBNBNeeded.String()) + + // create payment account and deposit + msgCreatePaymentAccount := &paymenttypes.MsgCreatePaymentAccount{ + Creator: userAddr, + } + _ = s.SendTxBlock(user, msgCreatePaymentAccount) + paymentAccountsReq := &paymenttypes.QueryGetPaymentAccountsByOwnerRequest{Owner: userAddr} + paymentAccounts, err := s.Client.PaymentQueryClient.GetPaymentAccountsByOwner(ctx, paymentAccountsReq) + s.Require().NoError(err) + s.T().Logf("paymentAccounts %s", core.YamlString(paymentAccounts)) + paymentAddr := paymentAccounts.PaymentAccounts[0] + s.Require().Lenf(paymentAccounts.PaymentAccounts, 1, "paymentAccounts %s", core.YamlString(paymentAccounts)) + + // deposit BNB needed + msgDeposit := &paymenttypes.MsgDeposit{ + Creator: user.GetAddr().String(), + To: paymentAddr, + Amount: paymentAccountBNBNeeded.MulRaw(2), // deposit more than needed + } + _ = s.SendTxBlock(user, msgDeposit) + + // create bucket + bucketName := storagetestutils.GenRandomBucketName() + msgCreateBucket := storagetypes.NewMsgCreateBucket( + user.GetAddr(), bucketName, storagetypes.VISIBILITY_TYPE_PUBLIC_READ, sp.OperatorKey.GetAddr(), + sdk.MustAccAddressFromHex(paymentAddr), math.MaxUint, nil, bucketChargedReadQuota) + msgCreateBucket.PrimarySpApproval.GlobalVirtualGroupFamilyId = gvg.FamilyId + msgCreateBucket.PrimarySpApproval.Sig, err = sp.ApprovalKey.Sign(msgCreateBucket.GetApprovalBytes()) + s.Require().NoError(err) + s.SendTxBlock(user, msgCreateBucket) + + // check payment account stream record + paymentAccountStreamRecord := s.getStreamRecord(paymentAddr) + s.T().Logf("paymentAccountStreamRecord %s", core.YamlString(paymentAccountStreamRecord)) + s.Require().Equal(expectedRate.String(), paymentAccountStreamRecord.NetflowRate.Neg().String()) + s.Require().Equal(paymentAccountStreamRecord.BufferBalance.String(), paymentAccountBNBNeeded.String()) + s.Require().Equal(paymentAccountStreamRecord.StaticBalance.String(), paymentAccountBNBNeeded.String()) + + time.Sleep(5 * time.Second) + + dynamicBalanceResp, err := s.Client.DynamicBalance(ctx, &paymenttypes.QueryDynamicBalanceRequest{Account: user.GetAddr().String()}) + s.Require().NoError(err) + s.Require().True(dynamicBalanceResp.DynamicBalance.LT(paymentAccountBNBNeeded)) + + // withdraw more than static balance + withdrawMsg := paymenttypes.NewMsgWithdraw(userAddr, paymentAddr, paymentAccountBNBNeeded) + s.SendTxBlockWithExpectErrorString(withdrawMsg, user, "not enough") + + // withdraw less than static balance + amount := sdk.NewInt(1000) + withdrawMsg = paymenttypes.NewMsgWithdraw(userAddr, paymentAddr, amount) + s.SendTxBlock(user, withdrawMsg) + paymentAccountStreamRecordAfter := s.getStreamRecord(paymentAddr) + s.T().Logf("paymentAccountStreamRecordAfter %s", core.YamlString(paymentAccountStreamRecordAfter)) + + staticBalanceChange := paymentAccountStreamRecord.NetflowRate.MulRaw(paymentAccountStreamRecordAfter.CrudTimestamp - paymentAccountStreamRecord.CrudTimestamp).Neg() + s.Require().Equal(paymentAccountStreamRecord.StaticBalance.Sub(paymentAccountStreamRecordAfter.StaticBalance).Int64(), amount.Add(staticBalanceChange).Int64()) } -func (s *PaymentTestSuite) TestDeleteBucketWithReadQuota() { +func (s *PaymentTestSuite) TestStorageBill_DeleteBucket_WithReadQuota() { var err error ctx := context.Background() - sp := s.BaseSuite.PickStorageProvider() + sp := s.PickStorageProvider() gvg, found := sp.GetFirstGlobalVirtualGroup() s.Require().True(found) - queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgroupmoduletypes.QueryGlobalVirtualGroupFamilyRequest{ + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ FamilyId: gvg.FamilyId, }) s.Require().NoError(err) @@ -1084,7 +957,7 @@ func (s *PaymentTestSuite) TestDeleteBucketWithReadQuota() { s.Require().NoError(err) s.SendTxBlock(user, msgCreateBucket) - streamRecordsBeforeDelete := s.GetStreamRecords(streamAddresses) + streamRecordsBeforeDelete := s.getStreamRecords(streamAddresses) s.T().Logf("streamRecordsBeforeDelete: %s", core.YamlString(streamRecordsBeforeDelete)) s.Require().NotEqual(streamRecordsBeforeDelete.User.NetflowRate.String(), "0") @@ -1093,18 +966,18 @@ func (s *PaymentTestSuite) TestDeleteBucketWithReadQuota() { s.SendTxBlock(user, msgDeleteBucket) // check the billing change - streamRecordsAfterDelete := s.GetStreamRecords(streamAddresses) + streamRecordsAfterDelete := s.getStreamRecords(streamAddresses) s.T().Logf("streamRecordsBeforeDelete: %s", core.YamlString(streamRecordsAfterDelete)) s.Require().Equal(streamRecordsAfterDelete.User.NetflowRate.String(), "0") } -func (s *PaymentTestSuite) TestStorageSmoke() { +func (s *PaymentTestSuite) TestStorageBill_Smoke() { var err error ctx := context.Background() - sp := s.BaseSuite.PickStorageProvider() + sp := s.PickStorageProvider() gvg, found := sp.GetFirstGlobalVirtualGroup() s.Require().True(found) - queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgroupmoduletypes.QueryGlobalVirtualGroupFamilyRequest{ + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ FamilyId: gvg.FamilyId, }) s.Require().NoError(err) @@ -1117,12 +990,10 @@ func (s *PaymentTestSuite) TestStorageSmoke() { gvg.VirtualPaymentAddress, paymenttypes.ValidatorTaxPoolAddress.String(), } - streamRecordsBeforeCreateBucket := s.GetStreamRecords(streamAddresses) + streamRecordsBeforeCreateBucket := s.getStreamRecords(streamAddresses) s.T().Logf("streamRecordsBeforeCreateBucket: %s", core.YamlString(streamRecordsBeforeCreateBucket)) - paymentParams, err := s.Client.PaymentQueryClient.Params(ctx, &paymenttypes.QueryParamsRequest{}) - s.T().Logf("paymentParams %s, err: %v", paymentParams, err) - s.Require().NoError(err) + params := s.queryParams() // create bucket bucketName := storagetestutils.GenRandomBucketName() @@ -1144,7 +1015,7 @@ func (s *PaymentTestSuite) TestStorageSmoke() { s.Require().NoError(err) s.T().Logf("user bank account %s", userBankAccount) - streamRecordsAfterCreateBucket := s.GetStreamRecords(streamAddresses) + streamRecordsAfterCreateBucket := s.getStreamRecords(streamAddresses) userStreamRecord := streamRecordsAfterCreateBucket.User s.Require().Equal(userStreamRecord.StaticBalance, sdkmath.ZeroInt()) @@ -1165,7 +1036,7 @@ func (s *PaymentTestSuite) TestStorageSmoke() { readPrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice readChargeRate := readPrice.MulInt(sdk.NewIntFromUint64(queryHeadBucketResponse.BucketInfo.ChargedReadQuota)).TruncateInt() s.T().Logf("readPrice: %s, readChargeRate: %s", readPrice, readChargeRate) - userTaxRate := paymentParams.Params.VersionedParams.ValidatorTaxRate.MulInt(readChargeRate).TruncateInt() + userTaxRate := params.VersionedParams.ValidatorTaxRate.MulInt(readChargeRate).TruncateInt() userTotalRate := readChargeRate.Add(userTaxRate) s.Require().Equal(userStreamRecord.NetflowRate.Abs(), userTotalRate) expectedOutFlows := []paymenttypes.OutFlow{ @@ -1231,11 +1102,12 @@ func (s *PaymentTestSuite) TestStorageSmoke() { s.Require().NoError(err) primaryStorePrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.StorePrice secondaryStorePrice := queryGetSecondarySpStorePriceByTime.SecondarySpStorePrice.StorePrice - chargeSize := s.GetChargeSize(queryHeadObjectResponse.ObjectInfo.PayloadSize) + chargeSize := s.getChargeSize(queryHeadObjectResponse.ObjectInfo.PayloadSize) expectedChargeRate := primaryStorePrice.Add(secondaryStorePrice.MulInt64(6)).MulInt(sdk.NewIntFromUint64(chargeSize)).TruncateInt() - expectedLockedBalance := expectedChargeRate.Mul(sdkmath.NewIntFromUint64(paymentParams.Params.VersionedParams.ReserveTime)) + expectedChargeRate = params.VersionedParams.ValidatorTaxRate.MulInt(expectedChargeRate).TruncateInt().Add(expectedChargeRate) + expectedLockedBalance := expectedChargeRate.Mul(sdkmath.NewIntFromUint64(params.VersionedParams.ReserveTime)) - streamRecordsAfterCreateObject := s.GetStreamRecords(streamAddresses) + streamRecordsAfterCreateObject := s.getStreamRecords(streamAddresses) s.T().Logf("streamRecordsAfterCreateObject %s", core.YamlString(streamRecordsAfterCreateObject)) userStreamAccountAfterCreateObj := streamRecordsAfterCreateObject.User @@ -1263,10 +1135,10 @@ func (s *PaymentTestSuite) TestStorageSmoke() { s.SendTxBlock(sp.SealKey, msgSealObject) // check bill after seal - streamRecordsAfterSeal := s.GetStreamRecords(streamAddresses) + streamRecordsAfterSeal := s.getStreamRecords(streamAddresses) s.T().Logf("streamRecordsAfterSeal %s", core.YamlString(streamRecordsAfterSeal)) s.Require().Equal(sdkmath.ZeroInt(), streamRecordsAfterSeal.User.LockBalance) - s.CheckStreamRecordsBeforeAndAfter(streamRecordsAfterCreateObject, streamRecordsAfterSeal, readPrice, readChargeRate, primaryStorePrice, secondaryStorePrice, chargeSize, uint64(payloadSize)) + s.checkStreamRecordsBeforeAndAfter(streamRecordsAfterCreateObject, streamRecordsAfterSeal, readPrice, readChargeRate, primaryStorePrice, secondaryStorePrice, chargeSize, uint64(payloadSize)) // query dynamic balance time.Sleep(3 * time.Second) @@ -1278,7 +1150,7 @@ func (s *PaymentTestSuite) TestStorageSmoke() { s.T().Logf("queryDynamicBalanceResponse %s", core.YamlString(queryDynamicBalanceResponse)) // create empty object - streamRecordsBeforeCreateEmptyObject := s.GetStreamRecords(streamAddresses) + streamRecordsBeforeCreateEmptyObject := s.getStreamRecords(streamAddresses) s.T().Logf("streamRecordsBeforeCreateEmptyObject %s", core.YamlString(streamRecordsBeforeCreateEmptyObject)) emptyObjectName := "sub_directory/" @@ -1292,10 +1164,10 @@ func (s *PaymentTestSuite) TestStorageSmoke() { s.Require().NoError(err) s.SendTxBlock(user, msgCreateEmptyObject) - streamRecordsAfterCreateEmptyObject := s.GetStreamRecords(streamAddresses) + streamRecordsAfterCreateEmptyObject := s.getStreamRecords(streamAddresses) s.T().Logf("streamRecordsAfterCreateEmptyObject %s", core.YamlString(streamRecordsAfterCreateEmptyObject)) - chargeSize = s.GetChargeSize(uint64(emptyPayloadSize)) - s.CheckStreamRecordsBeforeAndAfter(streamRecordsBeforeCreateEmptyObject, streamRecordsAfterCreateEmptyObject, readPrice, readChargeRate, primaryStorePrice, secondaryStorePrice, chargeSize, uint64(emptyPayloadSize)) + chargeSize = s.getChargeSize(uint64(emptyPayloadSize)) + s.checkStreamRecordsBeforeAndAfter(streamRecordsBeforeCreateEmptyObject, streamRecordsAfterCreateEmptyObject, readPrice, readChargeRate, primaryStorePrice, secondaryStorePrice, chargeSize, uint64(emptyPayloadSize)) // test query auto settle records queryAllAutoSettleRecordRequest := paymenttypes.QueryAllAutoSettleRecordRequest{} @@ -1310,165 +1182,2423 @@ func (s *PaymentTestSuite) TestStorageSmoke() { s.T().Logf("deleteObjectSimRes %v", deleteObjectSimRes.Result) } -// TestForceDeletion_DeleteAfterPriceChange will cover the following case: -// create an object, sp increase the price a lot, the object can be force deleted even the object's own has no enough balance. -func (s *PaymentTestSuite) TestForceDeletion_AfterPriceChange() { +func (s *PaymentTestSuite) TestStorageBill_DeleteObjectBucket_WithoutPriceChange() { + var err error ctx := context.Background() - // create bucket - user, bucketName := s.createBucket() - - // set storage price - sp := s.BaseSuite.PickStorageProviderByBucketName(bucketName) - priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ - SpAddr: sp.OperatorKey.GetAddr().String(), - Timestamp: 0, + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, }) s.Require().NoError(err) - s.T().Log("price", priceRes.SpStoragePrice) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] - // create & seal objects - _, _, objectName1, objectId1, checksums1 := s.createObject(user, bucketName) - s.sealObject(bucketName, objectName1, objectId1, checksums1) + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) - _, _, objectName2, objectId2, checksums2 := s.createObject(user, bucketName) - s.sealObject(bucketName, objectName2, objectId2, checksums2) + // create bucket + bucketName := s.createBucket(sp, user, 256) - // update new price - msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ - SpAddress: sp.OperatorKey.GetAddr().String(), - ReadPrice: priceRes.SpStoragePrice.ReadPrice, - FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, - StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), - } - s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + //simulate delete bucket gas + msgDeleteBucket := storagetypes.NewMsgDeleteBucket(user.GetAddr(), bucketName) + simulateResponse := s.SimulateTx(msgDeleteBucket, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + gas := gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))) + s.T().Log("total gas", "gas", gas) + + // create & seal objects + _, _, objectName1, objectId1, checksums1, _ := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) // for payment time.Sleep(2 * time.Second) - queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} - queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) - s.Require().NoError(err) - + //transfer gas msgSend := banktypes.NewMsgSend(user.GetAddr(), core.GenRandomAddr(), sdk.NewCoins( - sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.SubRaw(5*types.DecimalGwei)), + sdk.NewCoin(s.Config.Denom, sdkmath.NewInt(5*types.DecimalGwei)), )) + simulateResponse = s.SimulateTx(msgSend, user) + gasLimit = simulateResponse.GasInfo.GetGasUsed() + gasPrice, err = sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) - simulateResponse := s.SimulateTx(msgSend, user) - gasLimit := simulateResponse.GasInfo.GetGasUsed() - gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + gas = gas.Add(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit)))) + s.T().Log("total gas", "gas", gas) + + //delete object gas + msgDeleteObject := storagetypes.NewMsgDeleteObject(user.GetAddr(), bucketName, objectName1) + simulateResponse = s.SimulateTx(msgDeleteObject, user) + gasLimit = simulateResponse.GasInfo.GetGasUsed() + gasPrice, err = sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + gas = gas.Add(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit)))) + s.T().Log("total gas", "gas", gas) + + // transfer out user's balance + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) s.Require().NoError(err) msgSend.Amount = sdk.NewCoins( - sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))))), + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gas)), ) s.SendTxBlock(user, msgSend) - queryBalanceResponse, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) - s.Require().NoError(err) - s.Require().Equal(int64(0), queryBalanceResponse.Balance.Amount.Int64()) - - queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ - BucketName: bucketName, - ObjectName: objectName1, - } - queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + _, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) s.Require().NoError(err) - s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_SEALED) - // force delete bucket - msgDiscontinueBucket := storagetypes.NewMsgDiscontinueBucket(sp.GcKey.GetAddr(), bucketName, "test") - txRes := s.SendTxBlock(sp.GcKey, msgDiscontinueBucket) - deleteAt := filterDiscontinueBucketEventFromTx(txRes).DeleteAt + s.SendTxBlock(user, msgDeleteObject) + s.SendTxBlock(user, msgDeleteBucket) - for { - time.Sleep(200 * time.Millisecond) - statusRes, err := s.TmClient.TmClient.Status(context.Background()) - s.Require().NoError(err) - blockTime := statusRes.SyncInfo.LatestBlockTime.Unix() + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.NetflowRate, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) +} - s.T().Logf("current blockTime: %d, delete blockTime: %d", blockTime, deleteAt) +func (s *PaymentTestSuite) TestStorageBill_DeleteObjectBucket_WithPriceChange() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] - if blockTime > deleteAt { - break - } + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // create bucket + bucketName := s.createBucket(sp, user, 256) + + //simulate delete bucket gas + msgDeleteBucket := storagetypes.NewMsgDeleteBucket(user.GetAddr(), bucketName) + simulateResponse := s.SimulateTx(msgDeleteBucket, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + gas := gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))) + s.T().Log("total gas", "gas", gas) + + // create & seal objects + _, _, objectName1, objectId1, checksums1, _ := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + + // for payment + time.Sleep(2 * time.Second) + + //transfer gas + msgSend := banktypes.NewMsgSend(user.GetAddr(), core.GenRandomAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, sdkmath.NewInt(5*types.DecimalGwei)), + )) + simulateResponse = s.SimulateTx(msgSend, user) + gasLimit = simulateResponse.GasInfo.GetGasUsed() + gasPrice, err = sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + gas = gas.Add(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit)))) + s.T().Log("total gas", "gas", gas) + + //delete object gas + msgDeleteObject := storagetypes.NewMsgDeleteObject(user.GetAddr(), bucketName, objectName1) + simulateResponse = s.SimulateTx(msgDeleteObject, user) + gasLimit = simulateResponse.GasInfo.GetGasUsed() + gasPrice, err = sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + gas = gas.Add(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit)))) + s.T().Log("total gas", "gas", gas) + + // transfer out user's balance + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + msgSend.Amount = sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gas)), + ) + s.SendTxBlock(user, msgSend) + _, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + // sp price changes + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + s.T().Log("price", priceRes.SpStoragePrice) + + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice.MulInt64(1000), + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + s.SendTxBlock(user, msgDeleteObject) + s.SendTxBlock(user, msgDeleteBucket) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.NetflowRate, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // revert price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) +} + +func (s *PaymentTestSuite) TestStorageBill_DeleteObjectBucket_WithPriceChangeReserveTimeChange() { + defer s.revertParams() + + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // create bucket + bucketName := s.createBucket(sp, user, 256) + + //simulate delete bucket gas + msgDeleteBucket := storagetypes.NewMsgDeleteBucket(user.GetAddr(), bucketName) + simulateResponse := s.SimulateTx(msgDeleteBucket, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + gas := gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))) + s.T().Log("total gas", "gas", gas) + + // create & seal objects + _, _, objectName1, objectId1, checksums1, _ := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + + // for payment + time.Sleep(2 * time.Second) + + //transfer gas + msgSend := banktypes.NewMsgSend(user.GetAddr(), core.GenRandomAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, sdkmath.NewInt(5*types.DecimalGwei)), + )) + simulateResponse = s.SimulateTx(msgSend, user) + gasLimit = simulateResponse.GasInfo.GetGasUsed() + gasPrice, err = sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + gas = gas.Add(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit)))) + s.T().Log("total gas", "gas", gas) + + //delete object gas + msgDeleteObject := storagetypes.NewMsgDeleteObject(user.GetAddr(), bucketName, objectName1) + simulateResponse = s.SimulateTx(msgDeleteObject, user) + gasLimit = simulateResponse.GasInfo.GetGasUsed() + gasPrice, err = sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + gas = gas.Add(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit)))) + s.T().Log("total gas", "gas", gas) + + // transfer out user's balance + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + msgSend.Amount = sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gas)), + ) + s.SendTxBlock(user, msgSend) + _, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + // sp price changes + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + s.T().Log("price", priceRes.SpStoragePrice) + + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice.MulInt64(1000), + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + // update params + params := s.queryParams() + oldReserveTime := params.VersionedParams.ReserveTime + oldValidatorTaxRate := params.VersionedParams.ValidatorTaxRate + + params.VersionedParams.ReserveTime = oldReserveTime * 2 + params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate.MulInt64(2) + s.updateParams(params) + + s.SendTxBlock(user, msgDeleteObject) + s.SendTxBlock(user, msgDeleteBucket) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.NetflowRate, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // revert price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) +} + +func (s *PaymentTestSuite) TestStorageBill_DeleteObject_WithStoreLessThanReserveTime() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + params := s.queryParams() + reserveTime := params.VersionedParams.ReserveTime + + // create bucket + bucketName := s.createBucket(sp, user, 256) + + // create & seal objects + _, _, objectName1, objectId1, checksums1, payloadSize := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + + headObjectRes, err := s.Client.HeadObject(ctx, &storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + }) + s.Require().NoError(err) + s.T().Log("headObjectRes", headObjectRes) + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + _, _, userRateRead := s.calculateReadRates(sp, bucketName) + _, _, _, userRateStore := s.calculateStorageRates(sp, bucketName, objectName1, payloadSize, 0) + + msgDeleteObject := storagetypes.NewMsgDeleteObject(user.GetAddr(), bucketName, objectName1) + s.SendTxBlock(user, msgDeleteObject) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + + settledTime := streamRecordsAfter.User.CrudTimestamp - streamRecordsBefore.User.CrudTimestamp + timeToPay := int64(reserveTime) + headObjectRes.ObjectInfo.CreateAt - streamRecordsAfter.User.CrudTimestamp + balanceDelta := userRateRead.Add(userRateStore).MulRaw(settledTime).Add(userRateStore.MulRaw(timeToPay)) + + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), userRateStore.Int64()) + userBalanceChange := streamRecordsAfter.User.BufferBalance.Add(streamRecordsAfter.User.StaticBalance). + Sub(streamRecordsBefore.User.BufferBalance.Add(streamRecordsBefore.User.StaticBalance)) + s.Require().Equal(userBalanceChange.Neg().Int64(), balanceDelta.Int64()) + + familyDelta := streamRecordsAfter.GVGFamily.StaticBalance.Sub(streamRecordsBefore.GVGFamily.StaticBalance) + gvgDelta := streamRecordsAfter.GVG.StaticBalance.Sub(streamRecordsBefore.GVG.StaticBalance) + taxPoolDelta := streamRecordsAfter.Tax.StaticBalance.Sub(streamRecordsBefore.Tax.StaticBalance) + s.T().Log("familyDelta", familyDelta, "gvgDelta", gvgDelta, "taxPoolDelta", taxPoolDelta) + s.Require().True(familyDelta.Add(gvgDelta).Add(taxPoolDelta).Int64() >= balanceDelta.Int64()) // could exist other buckets/objects on the gvg & family +} + +func (s *PaymentTestSuite) TestStorageBill_DeleteObject_WithStoreMoreThanReserveTime() { + defer s.revertParams() + + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + params := s.queryParams() + params.VersionedParams.ReserveTime = 5 + params.ForcedSettleTime = 2 + s.updateParams(params) + + // create bucket + bucketName := s.createBucket(sp, user, 256) + + // create & seal objects + _, _, objectName1, objectId1, checksums1, payloadSize := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + + headObjectRes, err := s.Client.HeadObject(ctx, &storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + }) + s.Require().NoError(err) + s.T().Log("headObjectRes", headObjectRes) + + time.Sleep(5 * time.Second) + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + balanceBefore := queryBalanceResponse.Balance.Amount + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + _, _, userRateRead := s.calculateReadRates(sp, bucketName) + _, _, _, userRateStore := s.calculateStorageRates(sp, bucketName, objectName1, payloadSize, 0) + + msgDeleteObject := storagetypes.NewMsgDeleteObject(user.GetAddr(), bucketName, objectName1) + simulateResponse := s.SimulateTx(msgDeleteObject, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + gas := gasPrice.Amount.MulRaw(int64(gasLimit)) + + // delete object + s.SendTxBlock(user, msgDeleteObject) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + queryBalanceRequest = banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + balanceAfter := queryBalanceResponse.Balance.Amount + + settledTime := streamRecordsAfter.User.CrudTimestamp - streamRecordsBefore.User.CrudTimestamp + balanceDelta := userRateRead.Add(userRateStore).MulRaw(settledTime) + + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), userRateStore.Int64()) + userBalanceChange := streamRecordsBefore.User.BufferBalance.Add(streamRecordsBefore.User.StaticBalance). + Sub(streamRecordsAfter.User.BufferBalance.Add(streamRecordsAfter.User.StaticBalance)) + userBalanceChange = userBalanceChange.Add(balanceBefore.Sub(balanceAfter)).Sub(gas) + s.Require().Equal(userBalanceChange.Int64(), balanceDelta.Int64()) + + familyDelta := streamRecordsAfter.GVGFamily.StaticBalance.Sub(streamRecordsBefore.GVGFamily.StaticBalance) + gvgDelta := streamRecordsAfter.GVG.StaticBalance.Sub(streamRecordsBefore.GVG.StaticBalance) + taxPoolDelta := streamRecordsAfter.Tax.StaticBalance.Sub(streamRecordsBefore.Tax.StaticBalance) + s.T().Log("familyDelta", familyDelta, "gvgDelta", gvgDelta, "taxPoolDelta", taxPoolDelta) + s.Require().True(familyDelta.Add(gvgDelta).Add(taxPoolDelta).Int64() >= balanceDelta.Int64()) // could exist other buckets/objects on the gvg & family +} + +func (s *PaymentTestSuite) TestStorageBill_CreateBucket_WithZeroNoneZeroReadQuota() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + params := s.queryParams() + + // case: create bucket with zero read quota + bucketName := s.createBucket(sp, user, 0) + + // bucket created + queryHeadBucketRequest := storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + _, err = s.Client.HeadBucket(ctx, &queryHeadBucketRequest) + s.Require().NoError(err) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate, streamRecordsBefore.User.NetflowRate) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate, streamRecordsBefore.GVGFamily.NetflowRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate, streamRecordsBefore.Tax.NetflowRate) + + // case: create bucket with none zero read quota + bucketName = s.createBucket(sp, user, 10240) + + // bucket created + queryHeadBucketRequest = storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + queryHeadBucketResponse, err := s.Client.HeadBucket(ctx, &queryHeadBucketRequest) + s.Require().NoError(err) + + // check price and rate calculation + queryGetSpStoragePriceByTimeResp, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: queryHeadBucketResponse.BucketInfo.CreateAt, + }) + s.T().Logf("queryGetSpStoragePriceByTimeResp %s, err: %v", queryGetSpStoragePriceByTimeResp, err) + s.Require().NoError(err) + + readPrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice + readChargeRate := readPrice.MulInt(sdk.NewIntFromUint64(queryHeadBucketResponse.BucketInfo.ChargedReadQuota)).TruncateInt() + s.T().Logf("readPrice: %s, readChargeRate: %s", readPrice, readChargeRate) + taxRate := params.VersionedParams.ValidatorTaxRate.MulInt(readChargeRate).TruncateInt() + userTotalRate := readChargeRate.Add(taxRate) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), readChargeRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) + + expectedOutFlows := []paymenttypes.OutFlow{ + {ToAddress: family.VirtualPaymentAddress, Rate: readChargeRate}, + {ToAddress: paymenttypes.ValidatorTaxPoolAddress.String(), Rate: taxRate}, + } + userOutFlowsResponse, err := s.Client.OutFlows(ctx, &paymenttypes.QueryOutFlowsRequest{Account: user.GetAddr().String()}) + s.Require().NoError(err) + sort.Slice(userOutFlowsResponse.OutFlows, func(i, j int) bool { + return userOutFlowsResponse.OutFlows[i].ToAddress < userOutFlowsResponse.OutFlows[j].ToAddress + }) + sort.Slice(expectedOutFlows, func(i, j int) bool { + return expectedOutFlows[i].ToAddress < expectedOutFlows[j].ToAddress + }) + s.Require().Equal(expectedOutFlows, userOutFlowsResponse.OutFlows) +} + +func (s *PaymentTestSuite) TestStorageBill_CreateObject_WithZeroNoneZeroPayload() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + + bucketName := s.createBucket(sp, user, 0) + + // case: create object with zero payload size + streamRecordsBefore := s.getStreamRecords(streamAddresses) + _, _, objectName, _, _, payloadSize := s.createObject(user, bucketName, true) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + gvgFamilyRate, gvgRate, taxRate, userTotalRate := s.calculateStorageRates(sp, bucketName, objectName, payloadSize, 0) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRate) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) + + // case: create object with none zero payload size + streamRecordsBefore = s.getStreamRecords(streamAddresses) + _, _, objectName, _, _, payloadSize = s.createObject(user, bucketName, false) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + lockFee := s.calculateLockFee(sp, bucketName, objectName, payloadSize) + s.Require().Equal(streamRecordsAfter.User.LockBalance.Sub(streamRecordsBefore.User.LockBalance), lockFee) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) +} + +func (s *PaymentTestSuite) TestStorageBill_CreateObject_WithReserveTimeValidatorTaxRateChange() { + defer s.revertParams() + + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + + bucketName := s.createBucket(sp, user, 0) + + // create object with none zero payload size + streamRecordsBefore := s.getStreamRecords(streamAddresses) + _, _, objectName, _, _, payloadSize := s.createObject(user, bucketName, false) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + lockFee := s.calculateLockFee(sp, bucketName, objectName, payloadSize) + s.Require().Equal(streamRecordsAfter.User.LockBalance.Sub(streamRecordsBefore.User.LockBalance), lockFee) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // update params + + params := s.queryParams() + oldReserveTime := params.VersionedParams.ReserveTime + oldValidatorTaxRate := params.VersionedParams.ValidatorTaxRate + + params.VersionedParams.ReserveTime = oldReserveTime * 2 + params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate.MulInt64(2) + s.updateParams(params) + + // create another object after parameter changes + streamRecordsBefore = s.getStreamRecords(streamAddresses) + _, _, objectName, _, _, payloadSize = s.createObject(user, bucketName, false) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + lockFeeAfterParameterChange := s.calculateLockFee(sp, bucketName, objectName, payloadSize) + s.Require().Equal(streamRecordsAfter.User.LockBalance.Sub(streamRecordsBefore.User.LockBalance), lockFeeAfterParameterChange) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + s.Require().True(lockFeeAfterParameterChange.GT(lockFee.MulRaw(2))) +} + +func (s *PaymentTestSuite) TestStorageBill_CancelCreateObject() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + + bucketName := s.createBucket(sp, user, 0) + + // create object with none zero payload size + streamRecordsBefore := s.getStreamRecords(streamAddresses) + _, _, objectName, _, _, payloadSize := s.createObject(user, bucketName, false) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + lockFee := s.calculateLockFee(sp, bucketName, objectName, payloadSize) + s.Require().Equal(streamRecordsAfter.User.LockBalance.Sub(streamRecordsBefore.User.LockBalance), lockFee) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // cancel create object + s.cancelCreateObject(user, bucketName, objectName) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, lockFee) + s.Require().Equal(streamRecordsAfter.User.LockBalance.Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) +} + +func (s *PaymentTestSuite) TestStorageBill_SealObject_WithoutPriceChange() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + + bucketName := s.createBucket(sp, user, 0) + + // create object with none zero payload size + streamRecordsBefore := s.getStreamRecords(streamAddresses) + _, _, objectName, objectId, checksums, payloadSize := s.createObject(user, bucketName, false) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + lockFee := s.calculateLockFee(sp, bucketName, objectName, payloadSize) + s.Require().Equal(streamRecordsAfter.User.LockBalance.Sub(streamRecordsBefore.User.LockBalance), lockFee) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // case: seal object without price change + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + gvgFamilyRate, gvgRate, taxRate, userTotalRate := s.calculateStorageRates(sp, bucketName, objectName, payloadSize, 0) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRate) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) +} + +func (s *PaymentTestSuite) TestStorageBill_SealObject_WithPriceChange() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + + bucketName := s.createBucket(sp, user, 102400) + + // case: seal object with read price change and storage price change + _, _, objectName, objectId, checksums, payloadSize := s.createObject(user, bucketName, false) + + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice.MulInt64(2), + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(2), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + streamRecordsBefore := s.getStreamRecords(streamAddresses) + gvgFamilyRateReadBefore, taxRateReadBefore, userTotalRateReadBefore := s.calculateReadRates(sp, bucketName) + + // seal object + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + gvgFamilyRateReadAfter, taxRateReadAfter, userTotalRateReadAfter := s.calculateReadRatesCurrentTimestamp(sp, bucketName) + gvgFamilyRateStore, gvgRateStore, taxRateStore, userTotalRateStore := s.calculateStorageRatesCurrentTimestamp(sp, bucketName, objectName, payloadSize) + + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRateReadAfter.Sub(userTotalRateReadBefore).Add(userTotalRateStore).Neg()) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRateStore) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRateReadAfter.Sub(gvgFamilyRateReadBefore).Add(gvgFamilyRateStore)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRateReadAfter.Sub(taxRateReadBefore).Add(taxRateStore)) +} + +func (s *PaymentTestSuite) TestStorageBill_SealObject_WithPriceChangeValidatorTaxRateChange() { + defer s.revertParams() + + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + + bucketName := s.createBucket(sp, user, 102400) + + // case: seal object with read price change and storage price change + _, _, objectName, objectId, checksums, payloadSize := s.createObject(user, bucketName, false) + + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice.MulInt64(2), + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(2), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + streamRecordsBefore := s.getStreamRecords(streamAddresses) + gvgFamilyRateReadBefore, taxRateReadBefore, userTotalRateReadBefore := s.calculateReadRates(sp, bucketName) + + // seal object + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + gvgFamilyRateReadAfter, taxRateReadAfter, userTotalRateReadAfter := s.calculateReadRatesCurrentTimestamp(sp, bucketName) + gvgFamilyRateStore, gvgRateStore, taxRateStore, userTotalRateStore := s.calculateStorageRatesCurrentTimestamp(sp, bucketName, objectName, payloadSize) + + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRateReadAfter.Sub(userTotalRateReadBefore).Add(userTotalRateStore).Neg()) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRateStore) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRateReadAfter.Sub(gvgFamilyRateReadBefore).Add(gvgFamilyRateStore)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRateReadAfter.Sub(taxRateReadBefore).Add(taxRateStore)) + + // update params + params := s.queryParams() + oldReserveTime := params.VersionedParams.ReserveTime + oldValidatorTaxRate := params.VersionedParams.ValidatorTaxRate + + params.VersionedParams.ReserveTime = oldReserveTime * 2 + params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate.MulInt64(2) + s.updateParams(params) + + _, _, objectName, objectId, checksums, payloadSize = s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + gvgFamilyRateReadAfter, taxRateReadAfter, userTotalRateReadAfter = s.calculateReadRatesCurrentTimestamp(sp, bucketName) + gvgFamilyRateStore, gvgRateStore, taxRateStore, userTotalRateStore = s.calculateStorageRatesCurrentTimestamp(sp, bucketName, objectName, payloadSize) + + gvgFamilyRateStore = gvgFamilyRateStore.MulRaw(2) + gvgRateStore = gvgRateStore.MulRaw(2) + taxRateStore = taxRateStore.MulRaw(2) + userTotalRateStore = userTotalRateStore.MulRaw(2) + + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRateReadAfter.Sub(userTotalRateReadBefore).Add(userTotalRateStore).Neg()) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRateStore) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRateReadAfter.Sub(gvgFamilyRateReadBefore).Add(gvgFamilyRateStore)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRateReadAfter.Sub(taxRateReadBefore).Add(taxRateStore)) +} + +func (s *PaymentTestSuite) TestStorageBill_FullLifecycle() { + defer s.revertParams() + + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + // query storage price + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + s.T().Log("price", priceRes.SpStoragePrice) + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // full lifecycle + bucketName1 := s.createBucket(sp, user, 0) + _, _, objectName1, _, _, _ := s.createObject(user, bucketName1, true) + _, _, objectName2, objectId2, checksums2, _ := s.createObject(user, bucketName1, false) + s.sealObject(sp, gvg, bucketName1, objectName2, objectId2, checksums2) + + bucketName2 := s.createBucket(sp, user, 1024) + _, _, objectName3, objectId3, checksums3, _ := s.createObject(user, bucketName2, false) + s.sealObject(sp, gvg, bucketName2, objectName3, objectId3, checksums3) + + // update params + params := s.queryParams() + params.VersionedParams.ReserveTime = params.VersionedParams.ReserveTime * 3 + params.ForcedSettleTime = params.ForcedSettleTime * 2 + s.updateParams(params) + + _, _, objectName4, objectId4, checksums4, _ := s.createObject(user, bucketName2, false) + s.sealObject(sp, gvg, bucketName2, objectName4, objectId4, checksums4) + + bucketName3 := s.createBucket(sp, user, 1024) + _, _, objectName5, objectId5, checksums5, _ := s.createObject(user, bucketName3, false) + s.sealObject(sp, gvg, bucketName3, objectName5, objectId5, checksums5) + + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + // update params + params = s.queryParams() + params.VersionedParams.ReserveTime = params.VersionedParams.ReserveTime / 2 + params.ForcedSettleTime = params.ForcedSettleTime / 3 + s.updateParams(params) + + _, _, objectName6, objectId6, checksums6, _ := s.createObject(user, bucketName3, false) + s.sealObject(sp, gvg, bucketName3, objectName6, objectId6, checksums6) + + bucketName4 := s.createBucket(sp, user, 1024) + _, _, objectName7, objectId7, checksums7, _ := s.createObject(user, bucketName4, false) + s.sealObject(sp, gvg, bucketName4, objectName7, objectId7, checksums7) + + // update params + params = s.queryParams() + params.VersionedParams.ValidatorTaxRate = params.VersionedParams.ValidatorTaxRate.MulInt64(2) + s.updateParams(params) + + _, _, objectName8, objectId8, checksums8, _ := s.createObject(user, bucketName4, false) + s.sealObject(sp, gvg, bucketName4, objectName8, objectId8, checksums8) + + time.Sleep(3 * time.Second) + + _ = s.deleteObject(user, bucketName1, objectName1) + _ = s.deleteObject(user, bucketName1, objectName2) + + // update params + params = s.queryParams() + params.VersionedParams.ValidatorTaxRate = params.VersionedParams.ValidatorTaxRate.MulInt64(3) + s.updateParams(params) + + _ = s.deleteObject(user, bucketName2, objectName3) + _ = s.deleteObject(user, bucketName2, objectName4) + err = s.deleteBucket(user, bucketName1) + s.Require().Error(err) + err = s.deleteBucket(user, bucketName2) + s.Require().Error(err) + + _ = s.deleteObject(user, bucketName3, objectName5) + _ = s.deleteObject(user, bucketName3, objectName6) + _ = s.deleteObject(user, bucketName4, objectName7) + _ = s.deleteObject(user, bucketName4, objectName8) + err = s.deleteBucket(user, bucketName3) + s.Require().Error(err) + err = s.deleteBucket(user, bucketName4) + s.Require().Error(err) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().True(!streamRecordsAfter.User.StaticBalance.IsZero()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // revert price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) +} + +func (s *PaymentTestSuite) TestVirtualGroup_Settle() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + bucketName := s.createBucket(sp, user, 1024) + _, _, objectName, objectId, checksums, _ := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) + + // sleep seconds + time.Sleep(3 * time.Second) + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // settle gvg family + msgSettle := virtualgrouptypes.MsgSettle{ + StorageProvider: sp.FundingKey.GetAddr().String(), + GlobalVirtualGroupFamilyId: family.Id, + } + s.SendTxBlock(sp.FundingKey, &msgSettle) + + // settle gvg + var secondarySp *core.StorageProvider + for _, sp := range s.StorageProviders { + for _, id := range gvg.SecondarySpIds { + if sp.Info.Id == id { + secondarySp = sp + break + } + } + } + msgSettle = virtualgrouptypes.MsgSettle{ + StorageProvider: secondarySp.FundingKey.GetAddr().String(), + GlobalVirtualGroupFamilyId: 0, + GlobalVirtualGroupIds: []uint32{gvg.Id}, + } + s.SendTxBlock(secondarySp.FundingKey, &msgSettle) + + // assertions - balance has been checked in other tests in virtual group + streamRecordsAfter := s.getStreamRecords(streamAddresses) + + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) +} + +func (s *PaymentTestSuite) TestVirtualGroup_SwapOut() { + ctx := context.Background() + user := s.GenAndChargeAccounts(1, 1000000)[0] + successorSp := s.PickStorageProvider() + + // create a new storage provider + sp := s.BaseSuite.CreateNewStorageProvider() + s.T().Logf("new SP Info: %s", sp.Info.String()) + + // create a new gvg group for this storage provider + var secondarySPIDs []uint32 + for _, ssp := range s.StorageProviders { + if ssp.Info.Id != successorSp.Info.Id { + secondarySPIDs = append(secondarySPIDs, ssp.Info.Id) + } + if len(secondarySPIDs) == 6 { + break + } + } + + gvgID, familyID := s.BaseSuite.CreateGlobalVirtualGroup(sp, 0, secondarySPIDs, 1) + + // create object + s.BaseSuite.CreateObject(user, sp, gvgID, storagetestutils.GenRandomBucketName(), storagetestutils.GenRandomObjectName()) + + // Create another gvg contains this new sp + anotherSP := s.PickDifferentStorageProvider(successorSp.Info.Id) + var anotherSecondarySPIDs []uint32 + for _, ssp := range s.StorageProviders { + if ssp.Info.Id != successorSp.Info.Id && ssp.Info.Id != anotherSP.Info.Id { + anotherSecondarySPIDs = append(anotherSecondarySPIDs, ssp.Info.Id) + } + if len(anotherSecondarySPIDs) == 5 { + break + } + } + anotherSecondarySPIDs = append(anotherSecondarySPIDs, sp.Info.Id) + + anotherGVGID, _ := s.BaseSuite.CreateGlobalVirtualGroup(anotherSP, 0, anotherSecondarySPIDs, 1) + + familyResp, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{FamilyId: familyID}) + s.Require().NoError(err) + gvgResp, err := s.Client.GlobalVirtualGroup(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupRequest{GlobalVirtualGroupId: anotherGVGID}) + s.Require().NoError(err) + + streamAddresses := []string{ + user.GetAddr().String(), + familyResp.GlobalVirtualGroupFamily.VirtualPaymentAddress, + gvgResp.GlobalVirtualGroup.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // sp exit + s.SendTxBlock(sp.OperatorKey, &virtualgrouptypes.MsgStorageProviderExit{ + StorageProvider: sp.OperatorKey.GetAddr().String(), + }) + + resp, err := s.Client.StorageProvider(context.Background(), &sptypes.QueryStorageProviderRequest{Id: sp.Info.Id}) + s.Require().NoError(err) + s.Require().Equal(resp.StorageProvider.Status, sptypes.STATUS_GRACEFUL_EXITING) + + // swap out, as secondary sp + msgSwapOut2 := virtualgrouptypes.NewMsgSwapOut(sp.OperatorKey.GetAddr(), 0, []uint32{anotherGVGID}, successorSp.Info.Id) + msgSwapOut2.SuccessorSpApproval = &common.Approval{ExpiredHeight: math.MaxUint} + msgSwapOut2.SuccessorSpApproval.Sig, err = successorSp.ApprovalKey.Sign(msgSwapOut2.GetApprovalBytes()) + s.Require().NoError(err) + s.SendTxBlock(sp.OperatorKey, msgSwapOut2) + + // complete swap out + msgCompleteSwapOut2 := virtualgrouptypes.NewMsgCompleteSwapOut(successorSp.OperatorKey.GetAddr(), 0, []uint32{anotherGVGID}) + s.Require().NoError(err) + s.SendTxBlock(successorSp.OperatorKey, msgCompleteSwapOut2) + + // swap out, as primary sp + msgSwapOut := virtualgrouptypes.NewMsgSwapOut(sp.OperatorKey.GetAddr(), familyID, nil, successorSp.Info.Id) + msgSwapOut.SuccessorSpApproval = &common.Approval{ExpiredHeight: math.MaxUint} + msgSwapOut.SuccessorSpApproval.Sig, err = successorSp.ApprovalKey.Sign(msgSwapOut.GetApprovalBytes()) + s.Require().NoError(err) + s.SendTxBlock(sp.OperatorKey, msgSwapOut) + + // complete swap out, as primary sp + msgCompleteSwapOut := virtualgrouptypes.NewMsgCompleteSwapOut(successorSp.OperatorKey.GetAddr(), familyID, nil) + s.Require().NoError(err) + s.SendTxBlock(successorSp.OperatorKey, msgCompleteSwapOut) + + // sp complete exit success + s.SendTxBlock( + sp.OperatorKey, + &virtualgrouptypes.MsgCompleteStorageProviderExit{StorageProvider: sp.OperatorKey.GetAddr().String()}, + ) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) +} + +func (s *PaymentTestSuite) TestDiscontinue_InOneBlock_WithoutPriceChange() { + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // create bucket + bucketName := s.createBucket(sp, user, 0) + + // create & seal objects + _, _, objectName1, objectId1, checksums1, _ := s.createObject(user, bucketName, false) + _ = s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + + // for payment + time.Sleep(2 * time.Second) + + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + msgSend := banktypes.NewMsgSend(user.GetAddr(), core.GenRandomAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.SubRaw(5*types.DecimalGwei)), + )) + + simulateResponse := s.SimulateTx(msgSend, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + msgSend.Amount = sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))))), + ) + s.SendTxBlock(user, msgSend) + queryBalanceResponse, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + s.Require().Equal(int64(0), queryBalanceResponse.Balance.Amount.Int64()) + + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_SEALED) + + // force delete bucket + msgDiscontinueBucket := storagetypes.NewMsgDiscontinueBucket(sp.GcKey.GetAddr(), bucketName, "test") + txRes := s.SendTxBlock(sp.GcKey, msgDiscontinueBucket) + deleteAt := filterDiscontinueBucketEventFromTx(txRes).DeleteAt + + for { + time.Sleep(200 * time.Millisecond) + statusRes, err := s.TmClient.TmClient.Status(context.Background()) + s.Require().NoError(err) + blockTime := statusRes.SyncInfo.LatestBlockTime.Unix() + + s.T().Logf("current blockTime: %d, delete blockTime: %d", blockTime, deleteAt) + + if blockTime > deleteAt { + break + } + } + + _, err = s.Client.HeadBucket(ctx, &storagetypes.QueryHeadBucketRequest{BucketName: bucketName}) + s.Require().ErrorContains(err, "No such bucket") + + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) +} + +func (s *PaymentTestSuite) TestDiscontinue_InOneBlock_WithPriceChange() { + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // query storage price + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + s.T().Log("price", priceRes.SpStoragePrice) + + // create bucket + bucketName := s.createBucket(sp, user, 1200987) + + // create & seal objects + _, _, objectName1, objectId1, checksums1, _ := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + // for payment + time.Sleep(2 * time.Second) + + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + msgSend := banktypes.NewMsgSend(user.GetAddr(), core.GenRandomAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.SubRaw(5*types.DecimalGwei)), + )) + + simulateResponse := s.SimulateTx(msgSend, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + msgSend.Amount = sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))))), + ) + s.SendTxBlock(user, msgSend) + queryBalanceResponse, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + s.Require().Equal(int64(0), queryBalanceResponse.Balance.Amount.Int64()) + + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_SEALED) + + // force delete bucket + msgDiscontinueBucket := storagetypes.NewMsgDiscontinueBucket(sp.GcKey.GetAddr(), bucketName, "test") + txRes := s.SendTxBlock(sp.GcKey, msgDiscontinueBucket) + deleteAt := filterDiscontinueBucketEventFromTx(txRes).DeleteAt + + for { + time.Sleep(200 * time.Millisecond) + statusRes, err := s.TmClient.TmClient.Status(context.Background()) + s.Require().NoError(err) + blockTime := statusRes.SyncInfo.LatestBlockTime.Unix() + + s.T().Logf("current blockTime: %d, delete blockTime: %d", blockTime, deleteAt) + + if blockTime > deleteAt { + break + } + } + + _, err = s.Client.HeadBucket(ctx, &storagetypes.QueryHeadBucketRequest{BucketName: bucketName}) + s.Require().ErrorContains(err, "No such bucket") + + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // revert price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) +} + +func (s *PaymentTestSuite) TestDiscontinue_InBlocks_WithoutPriceChange() { + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // create bucket + bucketName := s.createBucket(sp, user, 12780) + + // create & seal objects + for i := 0; i < 4; i++ { + _, _, objectName1, objectId1, checksums1, _ := s.createObject(user, bucketName, false) + _ = s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_SEALED) + time.Sleep(200 * time.Millisecond) + } + + // for payment + time.Sleep(2 * time.Second) + + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + msgSend := banktypes.NewMsgSend(user.GetAddr(), core.GenRandomAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.SubRaw(5*types.DecimalGwei)), + )) + + simulateResponse := s.SimulateTx(msgSend, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + msgSend.Amount = sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))))), + ) + s.SendTxBlock(user, msgSend) + queryBalanceResponse, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + s.Require().Equal(int64(0), queryBalanceResponse.Balance.Amount.Int64()) + + // force delete bucket + msgDiscontinueBucket := storagetypes.NewMsgDiscontinueBucket(sp.GcKey.GetAddr(), bucketName, "test") + txRes := s.SendTxBlock(sp.GcKey, msgDiscontinueBucket) + deleteAt := filterDiscontinueBucketEventFromTx(txRes).DeleteAt + + for { + time.Sleep(200 * time.Millisecond) + statusRes, err := s.TmClient.TmClient.Status(context.Background()) + s.Require().NoError(err) + blockTime := statusRes.SyncInfo.LatestBlockTime.Unix() + + s.T().Logf("current blockTime: %d, delete blockTime: %d", blockTime, deleteAt) + + if blockTime > deleteAt { + break + } + } + + _, err = s.Client.HeadBucket(ctx, &storagetypes.QueryHeadBucketRequest{BucketName: bucketName}) + s.Require().ErrorContains(err, "No such bucket") + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) +} + +// TestDiscontinue_InBlocks_WithPriceChange will cover the following case: +// create an object, sp increase the price a lot, the object can be force deleted even the object's own has no enough balance. +func (s *PaymentTestSuite) TestDiscontinue_InBlocks_WithPriceChange() { + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // query storage price + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + s.T().Log("price", priceRes.SpStoragePrice) + + // create bucket + bucketName := s.createBucket(sp, user, 0) + + // create objects + for i := 0; i < 2; i++ { + _, _, objectName1, _, _, _ := s.createObject(user, bucketName, false) + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_CREATED) + time.Sleep(200 * time.Millisecond) + } + + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + // create & seal objects + for i := 0; i < 2; i++ { + _, _, objectName1, objectId1, checksums1, _ := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_SEALED) + time.Sleep(200 * time.Millisecond) + } + + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + msgSend := banktypes.NewMsgSend(user.GetAddr(), core.GenRandomAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.SubRaw(5*types.DecimalGwei)), + )) + + simulateResponse := s.SimulateTx(msgSend, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + msgSend.Amount = sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))))), + ) + s.SendTxBlock(user, msgSend) + queryBalanceResponse, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + s.Require().Equal(int64(0), queryBalanceResponse.Balance.Amount.Int64()) + + // for payment + time.Sleep(2 * time.Second) + + // force delete bucket + msgDiscontinueBucket := storagetypes.NewMsgDiscontinueBucket(sp.GcKey.GetAddr(), bucketName, "test") + txRes := s.SendTxBlock(sp.GcKey, msgDiscontinueBucket) + deleteAt := filterDiscontinueBucketEventFromTx(txRes).DeleteAt + + // update new price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice.MulInt64(100), + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + for { + time.Sleep(200 * time.Millisecond) + statusRes, err := s.TmClient.TmClient.Status(context.Background()) + s.Require().NoError(err) + blockTime := statusRes.SyncInfo.LatestBlockTime.Unix() + + s.T().Logf("current blockTime: %d, delete blockTime: %d", blockTime, deleteAt) + + if blockTime > deleteAt { + break + } + } + + _, err = s.Client.HeadBucket(ctx, &storagetypes.QueryHeadBucketRequest{BucketName: bucketName}) + s.Require().ErrorContains(err, "No such bucket") + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // revert price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) +} + +func (s *PaymentTestSuite) TestDiscontinue_InBlocks_WithPriceChangeReserveTimeChange() { + defer s.revertParams() + + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // query storage price + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + s.T().Log("price", priceRes.SpStoragePrice) + + // create bucket + bucketName := s.createBucket(sp, user, 10200) + + // create objects + for i := 0; i < 2; i++ { + _, _, objectName1, _, _, _ := s.createObject(user, bucketName, false) + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_CREATED) + time.Sleep(200 * time.Millisecond) + } + + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + // create & seal objects + for i := 0; i < 2; i++ { + _, _, objectName1, objectId1, checksums1, _ := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_SEALED) + time.Sleep(200 * time.Millisecond) + } + + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + msgSend := banktypes.NewMsgSend(user.GetAddr(), core.GenRandomAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.SubRaw(5*types.DecimalGwei)), + )) + + simulateResponse := s.SimulateTx(msgSend, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + msgSend.Amount = sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))))), + ) + s.SendTxBlock(user, msgSend) + queryBalanceResponse, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + s.Require().Equal(int64(0), queryBalanceResponse.Balance.Amount.Int64()) + + // update params + params := s.queryParams() + oldReserveTime := params.VersionedParams.ReserveTime + oldValidatorTaxRate := params.VersionedParams.ValidatorTaxRate + + params.VersionedParams.ReserveTime = oldReserveTime * 2 + params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate.MulInt64(2) + s.updateParams(params) + + // for payment + time.Sleep(2 * time.Second) + + // force delete bucket + msgDiscontinueBucket := storagetypes.NewMsgDiscontinueBucket(sp.GcKey.GetAddr(), bucketName, "test") + txRes := s.SendTxBlock(sp.GcKey, msgDiscontinueBucket) + deleteAt := filterDiscontinueBucketEventFromTx(txRes).DeleteAt + + // update new price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice.MulInt64(100), + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + for { + time.Sleep(200 * time.Millisecond) + statusRes, err := s.TmClient.TmClient.Status(context.Background()) + s.Require().NoError(err) + blockTime := statusRes.SyncInfo.LatestBlockTime.Unix() + + s.T().Logf("current blockTime: %d, delete blockTime: %d", blockTime, deleteAt) + + if blockTime > deleteAt { + break + } + } + + _, err = s.Client.HeadBucket(ctx, &storagetypes.QueryHeadBucketRequest{BucketName: bucketName}) + s.Require().ErrorContains(err, "No such bucket") + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().True(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64() <= int64(0)) // there are other auto settling + + // revert price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) +} + +func (s *PaymentTestSuite) TestDiscontinue_InBlocks_WithPriceChangeReserveTimeChange_FrozenAccount() { + defer s.revertParams() + + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 1000000)[0] + + // params + params := s.queryParams() + oldReserveTime := params.VersionedParams.ReserveTime + oldValidatorTaxRate := params.VersionedParams.ValidatorTaxRate + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + // query storage price + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + s.T().Log("price", priceRes.SpStoragePrice) + + // create bucket + bucketName := s.createBucket(sp, user, 0) + + // create objects + for i := 0; i < 2; i++ { + _, _, objectName1, _, _, _ := s.createObject(user, bucketName, false) + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_CREATED) + time.Sleep(200 * time.Millisecond) + } + + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + // update params + params.VersionedParams.ReserveTime = 8 + params.ForcedSettleTime = 5 + s.updateParams(params) + + // create & seal objects + for i := 0; i < 2; i++ { + _, _, objectName1, objectId1, checksums1, _ := s.createObject(user, bucketName, false) + s.sealObject(sp, gvg, bucketName, objectName1, objectId1, checksums1) + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName1, + } + queryHeadObjectResponse, err := s.Client.HeadObject(ctx, &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_SEALED) + time.Sleep(200 * time.Millisecond) + } + + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + + msgSend := banktypes.NewMsgSend(user.GetAddr(), core.GenRandomAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.SubRaw(5*types.DecimalGwei)), + )) + + simulateResponse := s.SimulateTx(msgSend, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + + msgSend.Amount = sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.Sub(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit))))), + ) + s.SendTxBlock(user, msgSend) + queryBalanceResponse, err = s.Client.BankQueryClient.Balance(ctx, &queryBalanceRequest) + s.Require().NoError(err) + s.Require().Equal(int64(0), queryBalanceResponse.Balance.Amount.Int64()) + + // wait account to be frozen + time.Sleep(8 * time.Second) + streamRecord := s.getStreamRecord(user.GetAddr().String()) + s.Require().True(streamRecord.Status == paymenttypes.STREAM_ACCOUNT_STATUS_FROZEN) + + // update params + params.VersionedParams.ReserveTime = oldReserveTime * 2 + params.VersionedParams.ValidatorTaxRate = oldValidatorTaxRate.MulInt64(2) + s.updateParams(params) + + // for payment + time.Sleep(2 * time.Second) + + // force delete bucket + msgDiscontinueBucket := storagetypes.NewMsgDiscontinueBucket(sp.GcKey.GetAddr(), bucketName, "test") + txRes := s.SendTxBlock(sp.GcKey, msgDiscontinueBucket) + deleteAt := filterDiscontinueBucketEventFromTx(txRes).DeleteAt + + // update new price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice.MulInt64(100), + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(10000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + for { + time.Sleep(200 * time.Millisecond) + statusRes, err := s.TmClient.TmClient.Status(context.Background()) + s.Require().NoError(err) + blockTime := statusRes.SyncInfo.LatestBlockTime.Unix() + + s.T().Logf("current blockTime: %d, delete blockTime: %d", blockTime, deleteAt) + + if blockTime > deleteAt { + break + } } _, err = s.Client.HeadBucket(ctx, &storagetypes.QueryHeadBucketRequest{BucketName: bucketName}) s.Require().ErrorContains(err, "No such bucket") + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().True(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64() <= int64(0)) // there are other auto settling + + // revert price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) +} + +func TestPaymentTestSuite(t *testing.T) { + suite.Run(t, new(PaymentTestSuite)) +} + +func (s *PaymentTestSuite) getStreamRecord(addr string) (sr paymenttypes.StreamRecord) { + ctx := context.Background() + streamRecordResp, err := s.Client.StreamRecord(ctx, &paymenttypes.QueryGetStreamRecordRequest{ + Account: addr, + }) + if streamRecordResp != nil { + s.Require().NoError(err) + sr = streamRecordResp.StreamRecord + } else { + s.Require().ErrorContainsf(err, "not found", "account: %s", addr) + sr.StaticBalance = sdk.ZeroInt() + sr.BufferBalance = sdk.ZeroInt() + sr.LockBalance = sdk.ZeroInt() + sr.NetflowRate = sdk.ZeroInt() + } + return sr +} + +func (s *PaymentTestSuite) getStreamRecords(addrs []string) (streamRecords StreamRecords) { + streamRecords.User = s.getStreamRecord(addrs[0]) + streamRecords.GVGFamily = s.getStreamRecord(addrs[1]) + streamRecords.GVG = s.getStreamRecord(addrs[2]) + streamRecords.Tax = s.getStreamRecord(addrs[3]) + s.T().Logf("streamRecords: %s", core.YamlString(streamRecords)) + return +} + +func (s *PaymentTestSuite) checkStreamRecordsBeforeAndAfter(streamRecordsBefore StreamRecords, streamRecordsAfter StreamRecords, readPrice sdk.Dec, + readChargeRate sdkmath.Int, primaryStorePrice sdk.Dec, secondaryStorePrice sdk.Dec, chargeSize uint64, payloadSize uint64) { + userRateDiff := streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate) + gvgFamilyRateDiff := streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate) + gvgRateDiff := streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate) + taxRateDiff := streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate) + s.Require().Equal(userRateDiff, gvgFamilyRateDiff.Add(gvgRateDiff).Add(taxRateDiff).Neg()) + + outFlowsResponse, err := s.Client.OutFlows(context.Background(), &paymenttypes.QueryOutFlowsRequest{Account: streamRecordsAfter.User.Account}) + s.Require().NoError(err) + userOutflowMap := lo.Reduce(outFlowsResponse.OutFlows, func(m map[string]sdkmath.Int, outflow paymenttypes.OutFlow, i int) map[string]sdkmath.Int { + m[outflow.ToAddress] = outflow.Rate + return m + }, make(map[string]sdkmath.Int)) + if payloadSize != 0 { + gvgFamilyRate := primaryStorePrice.MulInt(sdk.NewIntFromUint64(chargeSize)).TruncateInt().Add(readChargeRate) + s.Require().Equal(gvgFamilyRate, userOutflowMap[streamRecordsAfter.GVGFamily.Account]) + + gvgRate := secondaryStorePrice.MulInt(sdk.NewIntFromUint64(chargeSize)).TruncateInt().MulRaw(6) + s.Require().Equal(gvgRate, userOutflowMap[streamRecordsAfter.GVG.Account]) + } +} + +func (s *PaymentTestSuite) getChargeSize(payloadSize uint64) uint64 { + ctx := context.Background() + storageParams, err := s.Client.StorageQueryClient.Params(ctx, &storagetypes.QueryParamsRequest{}) + s.Require().NoError(err) + s.T().Logf("storageParams %s", storageParams) + minChargeSize := storageParams.Params.VersionedParams.MinChargeSize + if payloadSize < minChargeSize { + return minChargeSize + } else { + return payloadSize + } +} + +func (s *PaymentTestSuite) calculateLockFee(sp *core.StorageProvider, bucketName, objectName string, payloadSize uint64) sdkmath.Int { + ctx := context.Background() + + params := s.queryParams() + + headBucketExtraResponse, err := s.Client.HeadBucketExtra(ctx, &storagetypes.QueryHeadBucketExtraRequest{BucketName: bucketName}) + s.Require().NoError(err) + + storageParams, err := s.Client.StorageQueryClient.Params(ctx, &storagetypes.QueryParamsRequest{}) + s.T().Logf("storageParams %s, err: %v", storageParams, err) + s.Require().NoError(err) + secondarySpCount := storageParams.Params.VersionedParams.RedundantDataChunkNum + storageParams.Params.VersionedParams.RedundantParityChunkNum + + chargeSize := s.getChargeSize(payloadSize) + _, primaryPrice, secondaryPrice := s.getPrices(sp, headBucketExtraResponse.ExtraInfo.PriceTime) + + gvgFamilyRate := primaryPrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() + gvgRate := secondaryPrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() + gvgRate = gvgRate.MulRaw(int64(secondarySpCount)) + taxRate := params.VersionedParams.ValidatorTaxRate.MulInt(gvgFamilyRate.Add(gvgRate)).TruncateInt() + return gvgFamilyRate.Add(gvgRate).Add(taxRate).MulRaw(int64(params.VersionedParams.ReserveTime)) +} + +func (s *PaymentTestSuite) getPrices(sp *core.StorageProvider, timestamp int64) (sdk.Dec, sdk.Dec, sdk.Dec) { + ctx := context.Background() + + spStoragePriceByTimeResp, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: timestamp, + }) + s.T().Logf("spStoragePriceByTimeResp %s, err: %v", spStoragePriceByTimeResp, err) + s.Require().NoError(err) + + secondaryStoragePriceByTimeResp, err := s.Client.QueryGetSecondarySpStorePriceByTime(ctx, &sptypes.QueryGetSecondarySpStorePriceByTimeRequest{ + Timestamp: timestamp, + }) + s.T().Logf("spStoragePriceByTimeResp %s, err: %v", spStoragePriceByTimeResp, err) + s.Require().NoError(err) + + return spStoragePriceByTimeResp.SpStoragePrice.ReadPrice, spStoragePriceByTimeResp.SpStoragePrice.StorePrice, + secondaryStoragePriceByTimeResp.SecondarySpStorePrice.StorePrice +} + +func (s *PaymentTestSuite) calculateReadRates(sp *core.StorageProvider, bucketName string) (sdkmath.Int, sdkmath.Int, sdkmath.Int) { + ctx := context.Background() + + params := s.queryParams() + + headBucketRequest := storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + headBucketResponse, err := s.Client.HeadBucket(ctx, &headBucketRequest) + s.Require().NoError(err) + + readPrice, _, _ := s.getPrices(sp, headBucketResponse.BucketInfo.CreateAt) + + gvgFamilyRate := readPrice.MulInt64(int64(headBucketResponse.BucketInfo.ChargedReadQuota)).TruncateInt() + taxRate := params.VersionedParams.ValidatorTaxRate.MulInt(gvgFamilyRate).TruncateInt() + return gvgFamilyRate, taxRate, gvgFamilyRate.Add(taxRate) +} + +func (s *PaymentTestSuite) calculateReadRatesCurrentTimestamp(sp *core.StorageProvider, bucketName string) (sdkmath.Int, sdkmath.Int, sdkmath.Int) { + ctx := context.Background() + + params := s.queryParams() + + headBucketRequest := storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + headBucketResponse, err := s.Client.HeadBucket(ctx, &headBucketRequest) + s.Require().NoError(err) + + readPrice, _, _ := s.getPrices(sp, time.Now().Unix()) + + gvgFamilyRate := readPrice.MulInt64(int64(headBucketResponse.BucketInfo.ChargedReadQuota)).TruncateInt() + taxRate := params.VersionedParams.ValidatorTaxRate.MulInt(gvgFamilyRate).TruncateInt() + return gvgFamilyRate, taxRate, gvgFamilyRate.Add(taxRate) +} + +func (s *PaymentTestSuite) calculateStorageRates(sp *core.StorageProvider, bucketName, objectName string, payloadSize uint64, priceTime int64) (sdkmath.Int, sdkmath.Int, sdkmath.Int, sdkmath.Int) { + params := s.queryParams() + + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName, + } + headObjectResponse, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest) + s.Require().NoError(err) + secondarySpCount := len(headObjectResponse.GlobalVirtualGroup.SecondarySpIds) + fmt.Println("secondarySpCount", secondarySpCount) + if priceTime == 0 { + headBucketRequest := storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + headBucketResponse, err := s.Client.HeadBucket(context.Background(), &headBucketRequest) + s.Require().NoError(err) + priceTime = headBucketResponse.BucketInfo.CreateAt + } + + chargeSize := s.getChargeSize(payloadSize) + _, primaryPrice, secondaryPrice := s.getPrices(sp, priceTime) + s.T().Logf("===secondaryPrice: %v,primaryPrice: %v===", secondaryPrice, primaryPrice) + gvgFamilyRate := primaryPrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() + gvgRate := secondaryPrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() + gvgRate = gvgRate.MulRaw(int64(secondarySpCount)) + taxRate := params.VersionedParams.ValidatorTaxRate.MulInt(gvgFamilyRate.Add(gvgRate)).TruncateInt() + return gvgFamilyRate, gvgRate, taxRate, gvgFamilyRate.Add(gvgRate).Add(taxRate) +} + +func (s *PaymentTestSuite) calculateStorageRatesCurrentTimestamp(sp *core.StorageProvider, bucketName, objectName string, payloadSize uint64) (sdkmath.Int, sdkmath.Int, sdkmath.Int, sdkmath.Int) { + params := s.queryParams() + + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName, + } + headObjectResponse, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest) + s.Require().NoError(err) + secondarySpCount := len(headObjectResponse.GlobalVirtualGroup.SecondarySpIds) + fmt.Println("secondarySpCount", secondarySpCount) + + chargeSize := s.getChargeSize(payloadSize) + _, primaryPrice, secondaryPrice := s.getPrices(sp, time.Now().Unix()) + + gvgFamilyRate := primaryPrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() + gvgRate := secondaryPrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() + gvgRate = gvgRate.MulRaw(int64(secondarySpCount)) + taxRate := params.VersionedParams.ValidatorTaxRate.MulInt(gvgFamilyRate.Add(gvgRate)).TruncateInt() + return gvgFamilyRate, gvgRate, taxRate, gvgFamilyRate.Add(gvgRate).Add(taxRate) +} + +func (s *PaymentTestSuite) updateParams(params paymenttypes.Params) { + var err error + validator := s.Validator.GetAddr() + + ctx := context.Background() + + ts := time.Now().Unix() + queryParamsRequest := &paymenttypes.QueryParamsRequest{} + queryParamsResponse, err := s.Client.PaymentQueryClient.Params(ctx, queryParamsRequest) + s.Require().NoError(err) + s.T().Log("params before", core.YamlString(queryParamsResponse.Params)) + + msgUpdateParams := &paymenttypes.MsgUpdateParams{ + Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + Params: params, + } + + msgProposal, err := govtypesv1.NewMsgSubmitProposal( + []sdk.Msg{msgUpdateParams}, + sdk.Coins{sdk.NewCoin(s.BaseSuite.Config.Denom, types.NewIntFromInt64WithDecimal(100, types.DecimalBNB))}, + validator.String(), + "test", "test", "test", + ) + s.Require().NoError(err) + + txRes := s.SendTxBlock(s.Validator, msgProposal) + s.Require().Equal(txRes.Code, uint32(0)) - // revert price - msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ - SpAddress: sp.OperatorKey.GetAddr().String(), - ReadPrice: priceRes.SpStoragePrice.ReadPrice, - FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, - StorePrice: priceRes.SpStoragePrice.StorePrice, + // 3. query proposal and get proposal ID + var proposalId uint64 + for _, event := range txRes.Logs[0].Events { + if event.Type == "submit_proposal" { + for _, attr := range event.Attributes { + if attr.Key == "proposal_id" { + proposalId, err = strconv.ParseUint(attr.Value, 10, 0) + s.Require().NoError(err) + break + } + } + break + } } - s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + s.Require().True(proposalId != 0) + + queryProposal := &govtypesv1.QueryProposalRequest{ProposalId: proposalId} + _, err = s.Client.GovQueryClientV1.Proposal(ctx, queryProposal) + s.Require().NoError(err) + + // 4. submit MsgVote and wait the proposal exec + msgVote := govtypesv1.NewMsgVote(validator, proposalId, govtypesv1.OptionYes, "test") + txRes = s.SendTxBlock(s.Validator, msgVote) + s.Require().Equal(txRes.Code, uint32(0)) + + queryVoteParamsReq := govtypesv1.QueryParamsRequest{ParamsType: "voting"} + queryVoteParamsResp, err := s.Client.GovQueryClientV1.Params(ctx, &queryVoteParamsReq) + s.Require().NoError(err) + + // 5. wait a voting period and confirm that the proposal success. + s.T().Logf("voting period %s", *queryVoteParamsResp.Params.VotingPeriod) + time.Sleep(*queryVoteParamsResp.Params.VotingPeriod) + time.Sleep(1 * time.Second) + proposalRes, err := s.Client.GovQueryClientV1.Proposal(ctx, queryProposal) + s.Require().NoError(err) + s.Require().Equal(proposalRes.Proposal.Status, govtypesv1.ProposalStatus_PROPOSAL_STATUS_PASSED) + + queryParamsByTimestampRequest := &paymenttypes.QueryParamsByTimestampRequest{Timestamp: ts} + queryParamsByTimestampResponse, err := s.Client.PaymentQueryClient.ParamsByTimestamp(ctx, queryParamsByTimestampRequest) + s.Require().NoError(err) + s.T().Log("params by timestamp", core.YamlString(queryParamsResponse.Params)) + s.Require().Equal(queryParamsResponse.Params.VersionedParams.ReserveTime, + queryParamsByTimestampResponse.Params.VersionedParams.ReserveTime) + + queryParamsRequest = &paymenttypes.QueryParamsRequest{} + queryParamsResponse, err = s.Client.PaymentQueryClient.Params(ctx, queryParamsRequest) + s.Require().NoError(err) + s.T().Log("params after", core.YamlString(queryParamsResponse.Params)) } -func (s *PaymentTestSuite) GetStreamRecord(addr string) (sr paymenttypes.StreamRecord) { - ctx := context.Background() - streamRecordResp, err := s.Client.StreamRecord(ctx, &paymenttypes.QueryGetStreamRecordRequest{ - Account: addr, - }) - if streamRecordResp != nil { - s.Require().NoError(err) - sr = streamRecordResp.StreamRecord - } else { - s.Require().ErrorContainsf(err, "not found", "account: %s", addr) - sr.StaticBalance = sdk.ZeroInt() - sr.BufferBalance = sdk.ZeroInt() - sr.LockBalance = sdk.ZeroInt() - sr.NetflowRate = sdk.ZeroInt() +func (s *PaymentTestSuite) createBucketAndObject(sp *core.StorageProvider) (keys.KeyManager, string, string, storagetypes.Uint, [][]byte) { + var err error + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + + // CreateBucket + user := s.GenAndChargeAccounts(1, 1000000)[0] + bucketName := "ch" + storagetestutils.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) + + // CreateObject + objectName := storagetestutils.GenRandomObjectName() + // create test buffer + var buffer bytes.Buffer + line := `1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,123` + // Create 1MiB content where each line contains 1024 characters. + for i := 0; i < 1024; i++ { + buffer.WriteString(fmt.Sprintf("[%05d] %s\n", i, line)) } - return sr + 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(user, msgCreateObject) + + // HeadObject + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName, + } + queryHeadObjectResponse, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.ObjectName, objectName) + s.Require().Equal(queryHeadObjectResponse.ObjectInfo.BucketName, bucketName) + + return user, bucketName, objectName, queryHeadObjectResponse.ObjectInfo.Id, expectChecksum } -func (s *PaymentTestSuite) GetStreamRecords(addrs []string) (streamRecords StreamRecords) { - streamRecords.User = s.GetStreamRecord(addrs[0]) - streamRecords.GVGFamily = s.GetStreamRecord(addrs[1]) - streamRecords.GVG = s.GetStreamRecord(addrs[2]) - streamRecords.Tax = s.GetStreamRecord(addrs[3]) - return +func (s *PaymentTestSuite) createBucket(sp *core.StorageProvider, user keys.KeyManager, readQuota uint64) string { + var err error + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + + // CreateBucket + bucketName := "ch" + storagetestutils.GenRandomBucketName() + msgCreateBucket := storagetypes.NewMsgCreateBucket( + user.GetAddr(), bucketName, storagetypes.VISIBILITY_TYPE_PRIVATE, sp.OperatorKey.GetAddr(), + nil, math.MaxUint, nil, readQuota) + 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, + } + _, err = s.Client.HeadBucket(context.Background(), &queryHeadBucketRequest) + s.Require().NoError(err) + + return bucketName } -func (s *PaymentTestSuite) CheckStreamRecordsBeforeAndAfter(streamRecordsBefore StreamRecords, streamRecordsAfter StreamRecords, readPrice sdk.Dec, - readChargeRate sdkmath.Int, primaryStorePrice sdk.Dec, secondaryStorePrice sdk.Dec, chargeSize uint64, payloadSize uint64) { - userRateDiff := streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate) - gvgFamilyRateDiff := streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate) - gvgRateDiff := streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate) - taxRateDiff := streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate) - s.Require().Equal(userRateDiff, gvgFamilyRateDiff.Add(gvgRateDiff).Add(taxRateDiff).Neg()) +func (s *PaymentTestSuite) createObject(user keys.KeyManager, bucketName string, empty bool) (keys.KeyManager, string, string, storagetypes.Uint, [][]byte, uint64) { + var err error + sp := s.BaseSuite.PickStorageProviderByBucketName(bucketName) - outFlowsResponse, err := s.Client.OutFlows(context.Background(), &paymenttypes.QueryOutFlowsRequest{Account: streamRecordsAfter.User.Account}) + // CreateObject + objectName := storagetestutils.GenRandomObjectName() + // create test buffer + var buffer bytes.Buffer + line := `1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890,1234567890, + 1234567890,1234567890,1234567890,123` + // Create 1MiB content where each line contains 1024 characters. + if !empty { + for i := 0; i < 1024; i++ { + buffer.WriteString(fmt.Sprintf("[%05d] %s\n", i, line)) + } + } + payloadSize := uint64(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, 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) - userOutflowMap := lo.Reduce(outFlowsResponse.OutFlows, func(m map[string]sdkmath.Int, outflow paymenttypes.OutFlow, i int) map[string]sdkmath.Int { - m[outflow.ToAddress] = outflow.Rate - return m - }, make(map[string]sdkmath.Int)) - if payloadSize != 0 { - gvgFamilyRate := primaryStorePrice.MulInt(sdk.NewIntFromUint64(chargeSize)).TruncateInt().Add(readChargeRate) - s.Require().Equal(gvgFamilyRate, userOutflowMap[streamRecordsAfter.GVGFamily.Account]) + s.SendTxBlock(user, msgCreateObject) - gvgRate := secondaryStorePrice.MulInt(sdk.NewIntFromUint64(chargeSize)).TruncateInt().MulRaw(6) - s.Require().Equal(gvgRate, userOutflowMap[streamRecordsAfter.GVG.Account]) + // HeadObject + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName, } + headObjectResponse, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest) + s.Require().NoError(err) + s.Require().Equal(headObjectResponse.ObjectInfo.ObjectName, objectName) + s.Require().Equal(headObjectResponse.ObjectInfo.BucketName, bucketName) + + return user, bucketName, objectName, headObjectResponse.ObjectInfo.Id, expectChecksum, payloadSize } -func (s *PaymentTestSuite) GetChargeSize(payloadSize uint64) uint64 { - ctx := context.Background() - storageParams, err := s.Client.StorageQueryClient.Params(ctx, &storagetypes.QueryParamsRequest{}) +func (s *PaymentTestSuite) cancelCreateObject(user keys.KeyManager, bucketName, objectName string) { + msgCancelCreateObject := storagetypes.NewMsgCancelCreateObject(user.GetAddr(), bucketName, objectName) + s.SendTxBlock(user, msgCancelCreateObject) + + // HeadObject + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName, + } + _, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest) + s.Require().Error(err) +} + +func (s *PaymentTestSuite) sealObject(sp *core.StorageProvider, gvg *virtualgrouptypes.GlobalVirtualGroup, bucketName, objectName string, objectId storagetypes.Uint, checksums [][]byte) *virtualgrouptypes.GlobalVirtualGroup { + // 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, objectId, storagetypes.GenerateHash(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) - s.T().Logf("storageParams %s", storageParams) - minChargeSize := storageParams.Params.VersionedParams.MinChargeSize - if payloadSize < minChargeSize { - return minChargeSize - } else { - return payloadSize + msgSealObject.SecondarySpBlsAggSignatures = aggBlsSig + s.T().Logf("msg %s", msgSealObject.String()) + s.SendTxBlock(sp.SealKey, msgSealObject) + + queryHeadObjectRequest2 := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName, } + queryHeadObjectResponse2, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest2) + s.Require().NoError(err) + s.Require().Equal(queryHeadObjectResponse2.ObjectInfo.ObjectName, objectName) + s.Require().Equal(queryHeadObjectResponse2.ObjectInfo.BucketName, bucketName) + s.Require().Equal(queryHeadObjectResponse2.ObjectInfo.ObjectStatus, storagetypes.OBJECT_STATUS_SEALED) + + return gvg } -func TestPaymentTestSuite(t *testing.T) { - suite.Run(t, new(PaymentTestSuite)) +func (s *PaymentTestSuite) deleteObject(user keys.KeyManager, bucketName, objectName string) error { + msgDeleteObject := storagetypes.NewMsgDeleteObject(user.GetAddr(), bucketName, objectName) + s.SendTxBlock(user, msgDeleteObject) + + // HeadObject + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: bucketName, + ObjectName: objectName, + } + _, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest) + return err +} + +func (s *PaymentTestSuite) deleteBucket(user keys.KeyManager, bucketName string) error { + msgDeleteObject := storagetypes.NewMsgDeleteBucket(user.GetAddr(), bucketName) + s.SendTxBlock(user, msgDeleteObject) + + // HeadObject + queryHeadBucketRequest := storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + _, err := s.Client.HeadBucket(context.Background(), &queryHeadBucketRequest) + return err } func (s *PaymentTestSuite) TestUpdatePaymentParams() { @@ -1563,3 +3693,15 @@ CheckProposalStatus: s.T().Errorf("update params failed") } } + +func (s *PaymentTestSuite) revertParams() { + s.updateParams(s.defaultParams) +} + +func (s *PaymentTestSuite) queryParams() paymenttypes.Params { + queryParamsRequest := paymenttypes.QueryParamsRequest{} + queryParamsResponse, err := s.Client.PaymentQueryClient.Params(context.Background(), &queryParamsRequest) + s.Require().NoError(err) + s.T().Log("params", core.YamlString(queryParamsResponse.Params)) + return queryParamsResponse.Params +} diff --git a/e2e/tests/storage_bill_test.go b/e2e/tests/storage_bill_test.go new file mode 100644 index 000000000..4f063ebb3 --- /dev/null +++ b/e2e/tests/storage_bill_test.go @@ -0,0 +1,813 @@ +package tests + +import ( + "context" + "math" + "sort" + "time" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/prysmaticlabs/prysm/crypto/bls" + + "github.com/bnb-chain/greenfield/e2e/core" + "github.com/bnb-chain/greenfield/sdk/keys" + "github.com/bnb-chain/greenfield/sdk/types" + storagetestutils "github.com/bnb-chain/greenfield/testutil/storage" + paymenttypes "github.com/bnb-chain/greenfield/x/payment/types" + sptypes "github.com/bnb-chain/greenfield/x/sp/types" + storagetypes "github.com/bnb-chain/greenfield/x/storage/types" + virtualgrouptypes "github.com/bnb-chain/greenfield/x/virtualgroup/types" +) + +// TestStorageBill_CopyObject_WithoutPriceChange +func (s *PaymentTestSuite) TestStorageBill_CopyObject_WithoutPriceChange() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user0 := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user0.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + + paymentParams, err := s.Client.PaymentQueryClient.Params(ctx, &paymenttypes.QueryParamsRequest{}) + s.T().Logf("paymentParams %s, err: %v", paymentParams, err) + s.Require().NoError(err) + + bucketName := s.createBucket(sp, user0, 0) + + // create object with none zero payload size + streamRecordsBefore := s.getStreamRecords(streamAddresses) + _, _, objectName, objectId, checksums, payloadSize := s.createObject(user0, bucketName, false) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + lockFee := s.calculateLockFee(sp, bucketName, objectName, payloadSize) + s.Require().Equal(streamRecordsAfter.User.LockBalance.Sub(streamRecordsBefore.User.LockBalance), lockFee) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // case: seal object without price change + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + gvgFamilyRate, gvgRate, taxRate, userTotalRate := s.calculateStorageRates(sp, bucketName, objectName, payloadSize, 0) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRate) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) + + distBucketName := bucketName + distObjectName := storagetestutils.GenRandomObjectName() + + objectIfo, err := s.copyObject(user0, sp, bucketName, objectName, distBucketName, distObjectName) + s.Require().NoError(err) + s.sealObject(sp, gvg, distBucketName, distObjectName, objectIfo.Id, objectIfo.Checksums) + // assertions + streamRecordsAfterCopy := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfterCopy.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfterCopy.User.LockBalance, sdkmath.ZeroInt()) + //gvgFamilyRate1, gvgRate1, taxRate1, userTotalRate1 := s.calculateStorageRates(sp,distBucketName, distObjectName, payloadSize) + s.Require().Equal(streamRecordsAfterCopy.User.NetflowRate.Sub(streamRecordsAfter.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfterCopy.GVGFamily.NetflowRate.Sub(streamRecordsAfter.GVGFamily.NetflowRate), gvgFamilyRate) + s.Require().Equal(streamRecordsAfterCopy.GVG.NetflowRate.Sub(streamRecordsAfter.GVG.NetflowRate), gvgRate) + s.Require().Equal(streamRecordsAfterCopy.Tax.NetflowRate.Sub(streamRecordsAfter.Tax.NetflowRate), taxRate) +} + +// TestStorageBill_CopyObject_WithoutPriceChange +func (s *PaymentTestSuite) TestStorageBill_CopyObject_WithPriceChange() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user0 := s.GenAndChargeAccounts(1, 1000000)[0] + + streamAddresses := []string{ + user0.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + + paymentParams, err := s.Client.PaymentQueryClient.Params(ctx, &paymenttypes.QueryParamsRequest{}) + s.T().Logf("paymentParams %s, err: %v", paymentParams, err) + s.Require().NoError(err) + + bucketName := s.createBucket(sp, user0, 0) + + // create object with none zero payload size + streamRecordsBefore := s.getStreamRecords(streamAddresses) + _, _, objectName, objectId, checksums, payloadSize := s.createObject(user0, bucketName, false) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + lockFee := s.calculateLockFee(sp, bucketName, objectName, payloadSize) + s.Require().Equal(streamRecordsAfter.User.LockBalance.Sub(streamRecordsBefore.User.LockBalance), lockFee) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // case: seal object without price change + s.sealObject(sp, gvg, bucketName, objectName, objectId, checksums) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + gvgFamilyRate, gvgRate, taxRate, userTotalRate := s.calculateStorageRates(sp, bucketName, objectName, payloadSize, 0) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRate) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) + + priceRes, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: 0, + }) + s.Require().NoError(err) + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: priceRes.SpStoragePrice.ReadPrice, + FreeReadQuota: priceRes.SpStoragePrice.FreeReadQuota, + StorePrice: priceRes.SpStoragePrice.StorePrice.MulInt64(1000), + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + distBucketName := s.createBucket(sp, user0, 0) + distObjectName := storagetestutils.GenRandomObjectName() + objectIfo, err := s.copyObject(user0, sp, bucketName, objectName, distBucketName, distObjectName) + s.Require().NoError(err) + s.sealObject(sp, gvg, distBucketName, distObjectName, objectIfo.Id, objectIfo.Checksums) + // assertions + streamRecordsAfterCopy := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfterCopy.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfterCopy.User.LockBalance, sdkmath.ZeroInt()) + gvgFamilyRate1, gvgRate1, taxRate1, userTotalRate1 := s.calculateStorageRates(sp, distBucketName, distObjectName, payloadSize, 0) + s.Require().Equal(streamRecordsAfterCopy.GVGFamily.NetflowRate.Sub(streamRecordsAfter.GVGFamily.NetflowRate), gvgFamilyRate1) + s.Require().Equal(streamRecordsAfterCopy.GVG.NetflowRate.Sub(streamRecordsAfter.GVG.NetflowRate), gvgRate1) + s.Require().Equal(streamRecordsAfterCopy.Tax.NetflowRate.Sub(streamRecordsAfter.Tax.NetflowRate), taxRate1) + s.Require().Equal(streamRecordsAfterCopy.User.NetflowRate.Sub(streamRecordsAfter.User.NetflowRate).BigInt().String(), userTotalRate1.Neg().BigInt().String()) + +} + +// TestStorageBill_UpdateBucketQuota +func (s *PaymentTestSuite) TestStorageBill_UpdateBucketQuota() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + // recover price + defer s.SetSPPrice(sp, "12.34", "0") + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 10)[0] + + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + paymentParams, err := s.Client.PaymentQueryClient.Params(ctx, &paymenttypes.QueryParamsRequest{}) + s.T().Logf("paymentParams %s, err: %v", paymentParams, err) + s.Require().NoError(err) + + // case: create bucket with zero read quota + bucketName := s.createBucket(sp, user, 0) + + // bucket created + queryHeadBucketRequest := storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + _, err = s.Client.HeadBucket(ctx, &queryHeadBucketRequest) + s.Require().NoError(err) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate, streamRecordsBefore.User.NetflowRate) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate, streamRecordsBefore.GVGFamily.NetflowRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate, streamRecordsBefore.Tax.NetflowRate) + + readQuota := uint64(1024 * 1024 * 100) + // case: update bucket read quota + bucketInfo, err := s.updateBucket(user, bucketName, "", readQuota) + s.Require().NoError(err) + + // check price and rate calculation + queryGetSpStoragePriceByTimeResp, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: time.Now().Unix(), + }) + s.T().Logf("queryGetSpStoragePriceByTimeResp %s, err: %v", queryGetSpStoragePriceByTimeResp, err) + s.Require().NoError(err) + + readPrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice + readChargeRate := readPrice.MulInt(sdk.NewIntFromUint64(bucketInfo.ChargedReadQuota)).TruncateInt() + s.T().Logf("readPrice: %s, readChargeRate: %s", readPrice, readChargeRate) + taxRate := paymentParams.Params.VersionedParams.ValidatorTaxRate.MulInt(readChargeRate).TruncateInt() + userTotalRate := readChargeRate.Add(taxRate) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), readChargeRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) + + expectedOutFlows := []paymenttypes.OutFlow{ + {ToAddress: family.VirtualPaymentAddress, Rate: readChargeRate}, + {ToAddress: paymenttypes.ValidatorTaxPoolAddress.String(), Rate: taxRate}, + } + userOutFlowsResponse, err := s.Client.OutFlows(ctx, &paymenttypes.QueryOutFlowsRequest{Account: user.GetAddr().String()}) + s.Require().NoError(err) + sort.Slice(userOutFlowsResponse.OutFlows, func(i, j int) bool { + return userOutFlowsResponse.OutFlows[i].ToAddress < userOutFlowsResponse.OutFlows[j].ToAddress + }) + sort.Slice(expectedOutFlows, func(i, j int) bool { + return expectedOutFlows[i].ToAddress < expectedOutFlows[j].ToAddress + }) + s.Require().Equal(expectedOutFlows, userOutFlowsResponse.OutFlows) + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice.MulInt64(100), + FreeReadQuota: queryGetSpStoragePriceByTimeResp.SpStoragePrice.FreeReadQuota, + StorePrice: queryGetSpStoragePriceByTimeResp.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + // case: update bucket read quota + bucketInfo, err = s.updateBucket(user, bucketName, "", readQuota*2) + s.Require().NoError(err) + + // check price and rate calculation + queryGetSpStoragePriceByTimeResp, err = s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: time.Now().Unix(), + }) + s.T().Logf("queryGetSpStoragePriceByTimeResp %s, err: %v", queryGetSpStoragePriceByTimeResp, err) + s.Require().NoError(err) + + readPrice = queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice + readChargeRate = readPrice.MulInt(sdk.NewIntFromUint64(bucketInfo.ChargedReadQuota)).TruncateInt() + s.T().Logf("readPrice: %s, readChargeRate: %s", readPrice, readChargeRate) + taxRate = paymentParams.Params.VersionedParams.ValidatorTaxRate.MulInt(readChargeRate).TruncateInt() + userTotalRate = readChargeRate.Add(taxRate) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), readChargeRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) + + expectedOutFlows = []paymenttypes.OutFlow{ + {ToAddress: family.VirtualPaymentAddress, Rate: readChargeRate}, + {ToAddress: paymenttypes.ValidatorTaxPoolAddress.String(), Rate: taxRate}, + } + userOutFlowsResponse, err = s.Client.OutFlows(ctx, &paymenttypes.QueryOutFlowsRequest{Account: user.GetAddr().String()}) + s.Require().NoError(err) + sort.Slice(userOutFlowsResponse.OutFlows, func(i, j int) bool { + return userOutFlowsResponse.OutFlows[i].ToAddress < userOutFlowsResponse.OutFlows[j].ToAddress + }) + sort.Slice(expectedOutFlows, func(i, j int) bool { + return expectedOutFlows[i].ToAddress < expectedOutFlows[j].ToAddress + }) + s.Require().Equal(expectedOutFlows, userOutFlowsResponse.OutFlows) + // set big read price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice.MulInt64(1024 * 1024 * 1024), + FreeReadQuota: queryGetSpStoragePriceByTimeResp.SpStoragePrice.FreeReadQuota, + StorePrice: queryGetSpStoragePriceByTimeResp.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + chargedReadQuota := readQuota * 1024 * 1024 + msgUpdateBucketInfo := storagetypes.NewMsgUpdateBucketInfo( + user.GetAddr(), bucketName, &chargedReadQuota, user.GetAddr(), storagetypes.VISIBILITY_TYPE_PRIVATE) + + s.SendTxBlockWithExpectErrorString(msgUpdateBucketInfo, user, "apply user flows list failed") + +} + +// TestStorageBill_UpdatePaymentAddress +func (s *PaymentTestSuite) TestStorageBill_UpdatePaymentAddress() { + var err error + ctx := context.Background() + sp := s.PickStorageProvider() + defer s.SetSPPrice(sp, "12.34", "0") + gvg, found := sp.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user := s.GenAndChargeAccounts(1, 100)[0] + + paymentAccountAddr := s.CreatePaymentAccount(user, 1, 17) + paymentAcc := sdk.MustAccAddressFromHex(paymentAccountAddr) + streamAddresses := []string{ + user.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + streamRecordsBefore := s.getStreamRecords(streamAddresses) + + paymentParams, err := s.Client.PaymentQueryClient.Params(ctx, &paymenttypes.QueryParamsRequest{}) + s.T().Logf("paymentParams %s, err: %v", paymentParams, err) + s.Require().NoError(err) + + // case: create bucket with zero read quota + bucketName := s.createBucket(sp, user, 0) + + // bucket created + queryHeadBucketRequest := storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + _, err = s.Client.HeadBucket(ctx, &queryHeadBucketRequest) + s.Require().NoError(err) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate, streamRecordsBefore.User.NetflowRate) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate, streamRecordsBefore.GVGFamily.NetflowRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate, streamRecordsBefore.Tax.NetflowRate) + + readQuota := uint64(1024 * 100) + // case: update bucket read quota + bucketInfo, err := s.updateBucket(user, bucketName, "", readQuota) + s.Require().NoError(err) + + // check price and rate calculation + queryGetSpStoragePriceByTimeResp, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: time.Now().Unix(), + }) + s.T().Logf("queryGetSpStoragePriceByTimeResp %s, err: %v", queryGetSpStoragePriceByTimeResp, err) + s.Require().NoError(err) + + readPrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice + readChargeRate := readPrice.MulInt(sdk.NewIntFromUint64(bucketInfo.ChargedReadQuota)).TruncateInt() + s.T().Logf("readPrice: %s, readChargeRate: %s", readPrice, readChargeRate) + taxRate := paymentParams.Params.VersionedParams.ValidatorTaxRate.MulInt(readChargeRate).TruncateInt() + userTotalRate := readChargeRate.Add(taxRate) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), readChargeRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) + + expectedOutFlows := []paymenttypes.OutFlow{ + {ToAddress: family.VirtualPaymentAddress, Rate: readChargeRate}, + {ToAddress: paymenttypes.ValidatorTaxPoolAddress.String(), Rate: taxRate}, + } + userOutFlowsResponse, err := s.Client.OutFlows(ctx, &paymenttypes.QueryOutFlowsRequest{Account: user.GetAddr().String()}) + s.Require().NoError(err) + sort.Slice(userOutFlowsResponse.OutFlows, func(i, j int) bool { + return userOutFlowsResponse.OutFlows[i].ToAddress < userOutFlowsResponse.OutFlows[j].ToAddress + }) + sort.Slice(expectedOutFlows, func(i, j int) bool { + return expectedOutFlows[i].ToAddress < expectedOutFlows[j].ToAddress + }) + s.Require().Equal(expectedOutFlows, userOutFlowsResponse.OutFlows) + // update new price + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice.MulInt64(100), + FreeReadQuota: queryGetSpStoragePriceByTimeResp.SpStoragePrice.FreeReadQuota, + StorePrice: queryGetSpStoragePriceByTimeResp.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + // case: update bucket paymentAccountAddr + bucketInfo, err = s.updateBucket(user, bucketName, paymentAccountAddr, readQuota) + s.Require().NoError(err) + + // check price and rate calculation + queryGetSpStoragePriceByTimeResp, err = s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: time.Now().Unix(), + }) + s.T().Logf("queryGetSpStoragePriceByTimeResp %s, err: %v", queryGetSpStoragePriceByTimeResp, err) + s.Require().NoError(err) + + readPrice = queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice + readChargeRate = readPrice.MulInt(sdk.NewIntFromUint64(bucketInfo.ChargedReadQuota)).TruncateInt() + s.T().Logf("readPrice: %s, readChargeRate: %s", readPrice, readChargeRate) + taxRate = paymentParams.Params.VersionedParams.ValidatorTaxRate.MulInt(readChargeRate).TruncateInt() + userTotalRate = readChargeRate.Add(taxRate) + + // assertions + streamAddresses[0] = paymentAccountAddr + streamRecordsAfter = s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), readChargeRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) + + expectedOutFlows = []paymenttypes.OutFlow{ + {ToAddress: family.VirtualPaymentAddress, Rate: readChargeRate}, + {ToAddress: paymenttypes.ValidatorTaxPoolAddress.String(), Rate: taxRate}, + } + userOutFlowsResponse, err = s.Client.OutFlows(ctx, &paymenttypes.QueryOutFlowsRequest{Account: paymentAccountAddr}) + s.Require().NoError(err) + sort.Slice(userOutFlowsResponse.OutFlows, func(i, j int) bool { + return userOutFlowsResponse.OutFlows[i].ToAddress < userOutFlowsResponse.OutFlows[j].ToAddress + }) + sort.Slice(expectedOutFlows, func(i, j int) bool { + return expectedOutFlows[i].ToAddress < expectedOutFlows[j].ToAddress + }) + s.Require().Equal(expectedOutFlows, userOutFlowsResponse.OutFlows) + + // set big read price + msgUpdatePrice = &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice.MulInt64(1024 * 1024 * 1024), + FreeReadQuota: queryGetSpStoragePriceByTimeResp.SpStoragePrice.FreeReadQuota, + StorePrice: queryGetSpStoragePriceByTimeResp.SpStoragePrice.StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + + chargedReadQuota := readQuota * 1024 * 1024 * 1024 * 1024 + msgUpdateBucketInfo := storagetypes.NewMsgUpdateBucketInfo( + user.GetAddr(), bucketName, &chargedReadQuota, paymentAcc, storagetypes.VISIBILITY_TYPE_PRIVATE) + s.SendTxBlockWithExpectErrorString(msgUpdateBucketInfo, user, "apply user flows list failed") + // new payment account balance not enough + paymentAccountAddr = s.CreatePaymentAccount(user, 1, 13) + paymentAcc = sdk.MustAccAddressFromHex(paymentAccountAddr) + msgUpdateBucketInfo = storagetypes.NewMsgUpdateBucketInfo( + user.GetAddr(), bucketName, &readQuota, paymentAcc, storagetypes.VISIBILITY_TYPE_PRIVATE) + + s.SendTxBlockWithExpectErrorString(msgUpdateBucketInfo, user, "apply user flows list failed") + +} + +func (s *PaymentTestSuite) TestStorageBill_MigrationBucket() { + var err error + ctx := context.Background() + primarySP := s.PickStorageProvider() + s.SetSPPrice(primarySP, "1", "1.15") + + gvg, found := primarySP.GetFirstGlobalVirtualGroup() + s.Require().True(found) + queryFamilyResponse, err := s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: gvg.FamilyId, + }) + s.Require().NoError(err) + family := queryFamilyResponse.GlobalVirtualGroupFamily + user0 := s.GenAndChargeAccounts(1, 10)[0] + + streamAddresses := []string{ + user0.GetAddr().String(), + family.VirtualPaymentAddress, + gvg.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + + streamAddresses0 := streamAddresses + paymentParams, err := s.Client.PaymentQueryClient.Params(ctx, &paymenttypes.QueryParamsRequest{}) + s.T().Logf("paymentParams %s, err: %v", paymentParams, err) + s.Require().NoError(err) + + bucketName := s.createBucket(primarySP, user0, 0) + bucketInfo, err := s.Client.HeadBucket(context.Background(), &storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + }) + s.Require().NoError(err) + + // create object with none zero payload size + streamRecordsBefore := s.getStreamRecords(streamAddresses) + _, _, objectName, objectId, checksums, payloadSize := s.createObject(user0, bucketName, false) + + // assertions + streamRecordsAfter := s.getStreamRecords(streamAddresses) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + lockFee := s.calculateLockFee(primarySP, bucketName, objectName, payloadSize) + s.Require().Equal(streamRecordsAfter.User.LockBalance.Sub(streamRecordsBefore.User.LockBalance), lockFee) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate).Int64(), int64(0)) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate).Int64(), int64(0)) + + // case: seal object without price change + s.sealObject(primarySP, gvg, bucketName, objectName, objectId, checksums) + + // assertions + streamRecordsAfter = s.getStreamRecords(streamAddresses) + gvgFamilyRate, gvgRate, taxRate, userTotalRate := s.calculateStorageRates(primarySP, bucketName, objectName, payloadSize, 0) + s.T().Logf("gvgFamilyRate: %v, gvgRate: %v, taxRate: %v, userTotalRate: %v", gvgFamilyRate, gvgRate, taxRate, userTotalRate) + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Sub(streamRecordsBefore.User.NetflowRate), userTotalRate.Neg()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRate) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRate) + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate) + taxRate0 := taxRate + dstPrimarySP := s.CreateNewStorageProvider() + + s.SetSPPrice(dstPrimarySP, "2", "1.45") + _, secondarySPIDs := s.GetSecondarySP(dstPrimarySP, primarySP) + gvgID, _ := s.BaseSuite.CreateGlobalVirtualGroup(dstPrimarySP, 0, secondarySPIDs, 1) + gvgResp, err := s.Client.VirtualGroupQueryClient.GlobalVirtualGroup(context.Background(), &virtualgrouptypes.QueryGlobalVirtualGroupRequest{ + GlobalVirtualGroupId: gvgID, + }) + s.Require().NoError(err) + dstGVG := gvgResp.GlobalVirtualGroup + s.Require().True(found) + + queryFamilyResponse, err = s.Client.GlobalVirtualGroupFamily(ctx, &virtualgrouptypes.QueryGlobalVirtualGroupFamilyRequest{ + FamilyId: dstGVG.FamilyId, + }) + s.Require().NoError(err) + family = queryFamilyResponse.GlobalVirtualGroupFamily + streamAddresses = []string{ + user0.GetAddr().String(), + family.VirtualPaymentAddress, + dstGVG.VirtualPaymentAddress, + paymenttypes.ValidatorTaxPoolAddress.String(), + } + fundAddress := primarySP.FundingKey.GetAddr() + streamRecordsBefore = s.getStreamRecords(streamAddresses) + + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: fundAddress.String()} + fundBalanceBefore, err := s.Client.BankQueryClient.Balance(context.Background(), &queryBalanceRequest) + s.Require().NoError(err) + + // MigrationBucket + msgMigrationBucket, msgCompleteMigrationBucket := s.NewMigrateBucket(primarySP, dstPrimarySP, user0, bucketName, gvg.FamilyId, dstGVG.FamilyId, bucketInfo.BucketInfo.Id) + s.SendTxBlock(user0, msgMigrationBucket) + s.Require().NoError(err) + + // complete MigrationBucket + s.SendTxBlock(dstPrimarySP.OperatorKey, msgCompleteMigrationBucket) + streamRecordsAfter = s.getStreamRecords(streamAddresses) + fundBalanceAfter, err := s.Client.BankQueryClient.Balance(context.Background(), &queryBalanceRequest) + s.Require().NoError(err) + s.T().Logf("fundBalanceBefore: %v, fundBalanceAfter: %v, diff: %v", fundBalanceBefore, fundBalanceAfter, fundBalanceAfter.Balance.Amount.Sub(fundBalanceBefore.Balance.Amount)) + s.Require().True(fundBalanceAfter.Balance.Amount.Sub(fundBalanceBefore.Balance.Amount).GT(sdkmath.NewInt(0)), "migrate sp fund address need settle") + gvgFamilyRate, gvgRate, taxRate, userTotalRate = s.calculateStorageRates(dstPrimarySP, bucketName, objectName, payloadSize, time.Now().Unix()) + s.T().Logf("gvgFamilyRate: %v, gvgRate: %v, taxRate: %v, userTotalRate: %v", gvgFamilyRate, gvgRate, taxRate, userTotalRate) + s.T().Logf("NetflowRate: %v, userTotalRate: %v, actual taxRate diff: %v, expect taxRate diff: %v", streamRecordsAfter.User.NetflowRate.Neg(), userTotalRate.Neg(), streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate.Sub(taxRate0)) + + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRate) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRate) + // tax rate diff + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate.Sub(taxRate0)) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Neg(), userTotalRate.Abs()) + + s.SetSPPrice(primarySP, "12.3", "100") + + queryBalanceRequest.Address = dstPrimarySP.FundingKey.GetAddr().String() + fundBalanceBefore, err = s.Client.BankQueryClient.Balance(context.Background(), &queryBalanceRequest) + s.Require().NoError(err) + streamRecordsBefore = s.getStreamRecords(streamAddresses0) + // send msgMigrationBucket + msgMigrationBucket, msgCompleteMigrationBucket = s.NewMigrateBucket(dstPrimarySP, primarySP, user0, bucketName, dstGVG.FamilyId, gvg.FamilyId, bucketInfo.BucketInfo.Id) + + s.SendTxBlock(user0, msgMigrationBucket) + s.Require().NoError(err) + s.reduceBNBBalance(user0, s.Validator, sdkmath.NewIntWithDecimal(1, 1)) + + s.SendTxBlockWithExpectErrorString(msgCompleteMigrationBucket, primarySP.OperatorKey, "apply stream record changes for user failed") + + s.SetSPPrice(primarySP, "12.3", "13") + readPrice, primaryPrice, secondaryPrice := s.getPrices(primarySP, time.Now().Unix()) + s.T().Logf("readPrice: %v, primaryPrice: %v,secondaryPrice: %v", readPrice, primaryPrice, secondaryPrice) + + s.transferBNB(s.Validator, user0, sdkmath.NewIntWithDecimal(10000, 18)) + + s.SendTxBlock(primarySP.OperatorKey, msgCompleteMigrationBucket) + streamRecordsAfter = s.getStreamRecords(streamAddresses0) + fundBalanceAfter, err = s.Client.BankQueryClient.Balance(context.Background(), &queryBalanceRequest) + s.Require().NoError(err) + s.T().Logf("fundBalanceBefore: %v, fundBalanceAfter: %v, diff: %v", fundBalanceBefore, fundBalanceAfter, fundBalanceAfter.Balance.Amount.Sub(fundBalanceBefore.Balance.Amount)) + s.Require().True(fundBalanceAfter.Balance.Amount.Sub(fundBalanceBefore.Balance.Amount).GT(sdkmath.NewInt(0)), "migrate sp fund address need settle") + taxRate1 := taxRate + gvgFamilyRate, gvgRate, taxRate, userTotalRate = s.calculateStorageRates(primarySP, bucketName, objectName, payloadSize, time.Now().Unix()) + s.T().Logf("gvgFamilyRate: %v, gvgRate: %v, taxRate: %v, userTotalRate: %v", gvgFamilyRate, gvgRate, taxRate, userTotalRate) + s.T().Logf("NetflowRate: %v, userTotalRate: %v, actual taxRate diff: %v, expect taxRate diff: %v", streamRecordsAfter.User.NetflowRate.Neg(), userTotalRate.Neg(), streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate.Sub(taxRate0)) + + s.Require().Equal(streamRecordsAfter.User.StaticBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.User.LockBalance, sdkmath.ZeroInt()) + s.Require().Equal(streamRecordsAfter.GVGFamily.NetflowRate.Sub(streamRecordsBefore.GVGFamily.NetflowRate), gvgFamilyRate) + s.Require().Equal(streamRecordsAfter.GVG.NetflowRate.Sub(streamRecordsBefore.GVG.NetflowRate), gvgRate) + // tax rate diff + s.Require().Equal(streamRecordsAfter.Tax.NetflowRate.Sub(streamRecordsBefore.Tax.NetflowRate), taxRate.Sub(taxRate1)) + s.Require().Equal(streamRecordsAfter.User.NetflowRate.Neg(), userTotalRate.Abs()) +} + +func (s *PaymentTestSuite) GetSecondarySP(sps ...*core.StorageProvider) ([]*core.StorageProvider, []uint32) { + var secondarySPs []*core.StorageProvider + var secondarySPIDs []uint32 + + for _, ssp := range s.StorageProviders { + isSecondSP := true + for _, sp := range sps { + if ssp.Info.Id == sp.Info.Id { + isSecondSP = false + break + } + } + if isSecondSP { + secondarySPIDs = append(secondarySPIDs, ssp.Info.Id) + secondarySPs = append(secondarySPs, ssp) + } + if len(secondarySPIDs) == 6 { + break + } + } + return secondarySPs, secondarySPIDs +} +func (s *PaymentTestSuite) NewMigrateBucket(srcSP, dstSP *core.StorageProvider, user keys.KeyManager, bucketName string, srcID, dstID uint32, bucketID sdkmath.Uint) (*storagetypes.MsgMigrateBucket, *storagetypes.MsgCompleteMigrateBucket) { + + secondarySPs, _ := s.GetSecondarySP(srcSP, dstSP) + var gvgMappings []*storagetypes.GVGMapping + gvgMappings = append(gvgMappings, &storagetypes.GVGMapping{SrcGlobalVirtualGroupId: srcID, DstGlobalVirtualGroupId: dstID}) + for _, gvgMapping := range gvgMappings { + migrationBucketSignHash := storagetypes.NewSecondarySpMigrationBucketSignDoc(s.GetChainID(), bucketID, dstSP.Info.Id, gvgMapping.SrcGlobalVirtualGroupId, gvgMapping.DstGlobalVirtualGroupId).GetBlsSignHash() + secondarySigs := make([][]byte, 0) + secondarySPBlsPubKeys := make([]bls.PublicKey, 0) + for _, ssp := range secondarySPs { + sig, err := core.BlsSignAndVerify(ssp, migrationBucketSignHash) + s.Require().NoError(err) + secondarySigs = append(secondarySigs, sig) + pk, err := bls.PublicKeyFromBytes(ssp.BlsKey.PubKey().Bytes()) + s.Require().NoError(err) + secondarySPBlsPubKeys = append(secondarySPBlsPubKeys, pk) + } + aggBlsSig, err := core.BlsAggregateAndVerify(secondarySPBlsPubKeys, migrationBucketSignHash, secondarySigs) + s.Require().NoError(err) + gvgMapping.SecondarySpBlsSignature = aggBlsSig + } + + msgMigrationBucket := storagetypes.NewMsgMigrateBucket(user.GetAddr(), bucketName, dstSP.Info.Id) + msgMigrationBucket.DstPrimarySpApproval.ExpiredHeight = math.MaxInt + msgMigrationBucket.DstPrimarySpApproval.Sig, _ = dstSP.ApprovalKey.Sign(msgMigrationBucket.GetApprovalBytes()) + + msgCompleteMigrationBucket := storagetypes.NewMsgCompleteMigrateBucket(dstSP.OperatorKey.GetAddr(), bucketName, dstID, gvgMappings) + + return msgMigrationBucket, msgCompleteMigrationBucket + +} +func (s *PaymentTestSuite) SetSPPrice(sp *core.StorageProvider, readPrice, storePrice string) { + ctx := context.Background() + + queryGetSpStoragePriceByTimeResp, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: time.Now().Unix(), + }) + s.Require().NoError(err) + ReadPrice, err := sdk.NewDecFromStr(readPrice) + s.Require().NoError(err) + StorePrice := queryGetSpStoragePriceByTimeResp.SpStoragePrice.StorePrice + if storePrice != "0" { + StorePrice, err = sdk.NewDecFromStr(storePrice) + s.Require().NoError(err) + } + + msgUpdatePrice := &sptypes.MsgUpdateSpStoragePrice{ + SpAddress: sp.OperatorKey.GetAddr().String(), + ReadPrice: ReadPrice, + FreeReadQuota: queryGetSpStoragePriceByTimeResp.SpStoragePrice.FreeReadQuota, + StorePrice: StorePrice, + } + s.SendTxBlock(sp.OperatorKey, msgUpdatePrice) + queryGetSpStoragePriceByTimeResp, err = s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{ + SpAddr: sp.OperatorKey.GetAddr().String(), + Timestamp: time.Now().Unix(), + }) + s.Require().NoError(err) + s.T().Logf("queryGetSpStoragePriceByTimeResp read price: %s, store price: %v", + queryGetSpStoragePriceByTimeResp.SpStoragePrice.ReadPrice, queryGetSpStoragePriceByTimeResp.SpStoragePrice.StorePrice) + +} + +// CreatePaymentAccount create new payment account and return latest payment account +func (s *PaymentTestSuite) CreatePaymentAccount(user keys.KeyManager, amount, decimal int64) string { + ctx := context.Background() + // create a new payment account + msgCreatePaymentAccount := &paymenttypes.MsgCreatePaymentAccount{ + Creator: user.GetAddr().String(), + } + _ = s.SendTxBlock(user, msgCreatePaymentAccount) + // query user's payment accounts + queryGetPaymentAccountsByOwnerRequest := paymenttypes.QueryGetPaymentAccountsByOwnerRequest{ + Owner: user.GetAddr().String(), + } + paymentAccounts, err := s.Client.GetPaymentAccountsByOwner(ctx, &queryGetPaymentAccountsByOwnerRequest) + s.Require().NoError(err) + s.T().Log(paymentAccounts) + paymentAccountAddr := paymentAccounts.PaymentAccounts[len(paymentAccounts.PaymentAccounts)-1] + // charge payment account + paymentAcc := sdk.MustAccAddressFromHex(paymentAccountAddr) + msgSend := banktypes.NewMsgSend(user.GetAddr(), paymentAcc, []sdk.Coin{{Denom: "BNB", Amount: types.NewIntFromInt64WithDecimal(amount, decimal)}}) + s.SendTxBlock(user, msgSend) + + return paymentAccountAddr +} + +func (s *PaymentTestSuite) copyObject(user keys.KeyManager, sp *core.StorageProvider, bucketName, objectName, dstBucketName, dstObjectName string) (*storagetypes.ObjectInfo, error) { + msgCopyObject := storagetypes.NewMsgCopyObject(user.GetAddr(), bucketName, dstBucketName, objectName, dstObjectName, math.MaxUint, nil) + msgCopyObject.DstPrimarySpApproval.Sig, _ = sp.ApprovalKey.Sign(msgCopyObject.GetApprovalBytes()) + + s.SendTxBlock(user, msgCopyObject) + // HeadObject + queryHeadObjectRequest := storagetypes.QueryHeadObjectRequest{ + BucketName: dstBucketName, + ObjectName: dstObjectName, + } + headObjectResponse, err := s.Client.HeadObject(context.Background(), &queryHeadObjectRequest) + return headObjectResponse.ObjectInfo, err + +} + +func (s *PaymentTestSuite) updateBucket(user keys.KeyManager, bucketName string, paymentAddress string, chargedReadQuota uint64) (*storagetypes.BucketInfo, error) { + + msgUpdateBucketInfo := storagetypes.NewMsgUpdateBucketInfo( + user.GetAddr(), bucketName, &chargedReadQuota, user.GetAddr(), storagetypes.VISIBILITY_TYPE_PRIVATE) + if paymentAddress != "" { + msgUpdateBucketInfo.PaymentAddress = paymentAddress + } + s.SendTxBlock(user, msgUpdateBucketInfo) + + queryHeadObjectRequest := storagetypes.QueryHeadBucketRequest{ + BucketName: bucketName, + } + headObjectResponse, err := s.Client.HeadBucket(context.Background(), &queryHeadObjectRequest) + return headObjectResponse.BucketInfo, err + +} + +func (s *PaymentTestSuite) reduceBNBBalance(user, to keys.KeyManager, leftBalance sdkmath.Int) { + queryBalanceRequest := banktypes.QueryBalanceRequest{Denom: s.Config.Denom, Address: user.GetAddr().String()} + queryBalanceResponse, err := s.Client.BankQueryClient.Balance(context.Background(), &queryBalanceRequest) + s.Require().NoError(err) + + msgSend := banktypes.NewMsgSend(user.GetAddr(), to.GetAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, queryBalanceResponse.Balance.Amount.SubRaw(5*types.DecimalGwei)), + )) + + simulateResponse := s.SimulateTx(msgSend, user) + gasLimit := simulateResponse.GasInfo.GetGasUsed() + gasPrice, err := sdk.ParseCoinNormalized(simulateResponse.GasInfo.GetMinGasPrice()) + s.Require().NoError(err) + sendBNBAmount := queryBalanceResponse.Balance.Amount.Sub(gasPrice.Amount.Mul(sdk.NewInt(int64(gasLimit)))).Sub(leftBalance) + msgSend.Amount = sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, sendBNBAmount), + ) + s.SendTxBlock(user, msgSend) + queryBalanceResponse, err = s.Client.BankQueryClient.Balance(context.Background(), &queryBalanceRequest) + s.Require().NoError(err) + s.T().Logf("balance: %v", queryBalanceResponse.Balance.Amount) +} + +func (s *PaymentTestSuite) transferBNB(user, to keys.KeyManager, amount sdkmath.Int) { + + msgSend := banktypes.NewMsgSend(user.GetAddr(), to.GetAddr(), sdk.NewCoins( + sdk.NewCoin(s.Config.Denom, amount), + )) + s.SendTxBlock(user, msgSend) + +} diff --git a/e2e/tests/virtualgroup_test.go b/e2e/tests/virtualgroup_test.go index 2a724af05..91afee122 100644 --- a/e2e/tests/virtualgroup_test.go +++ b/e2e/tests/virtualgroup_test.go @@ -381,7 +381,13 @@ func (s *VirtualGroupTestSuite) TestSPExit() { s.BaseSuite.CreateObject(user, sp, gvgID, storagetestutil.GenRandomBucketName(), storagetestutil.GenRandomObjectName()) // 4. Create another gvg contains this new sp - anotherSP := s.StorageProviders[1] + var anotherSP *core.StorageProvider + for _, tsp := range s.StorageProviders { + if tsp.Info.Id != sp.Info.Id && tsp.Info.Id != successorSp.Info.Id { + anotherSP = tsp + break + } + } var anotherSecondarySPIDs []uint32 for _, ssp := range s.StorageProviders { if ssp.Info.Id != successorSp.Info.Id && ssp.Info.Id != anotherSP.Info.Id { diff --git a/proto/greenfield/storage/query.proto b/proto/greenfield/storage/query.proto index ad2bb14eb..411f7f931 100644 --- a/proto/greenfield/storage/query.proto +++ b/proto/greenfield/storage/query.proto @@ -118,6 +118,16 @@ service Query { option (google.api.http).get = "/greenfield/storage/lock_fee"; } + // Queries a bucket extra info (with gvg bindings and price time) with specify name. + rpc HeadBucketExtra(QueryHeadBucketExtraRequest) returns (QueryHeadBucketExtraResponse) { + option (google.api.http).get = "/greenfield/storage/head_bucket_extra/{bucket_name}"; + } + + // Queries whether read and storage prices changed for the bucket. + rpc QueryIsPriceChanged(QueryIsPriceChangedRequest) returns (QueryIsPriceChangedResponse) { + option (google.api.http).get = "/greenfield/storage/is_price_changed/{bucket_name}"; + } + // this line is used by starport scaffolding # 2 } @@ -291,4 +301,59 @@ message QueryLockFeeResponse { ]; } +message QueryHeadBucketExtraRequest { + string bucket_name = 1; +} + +message QueryHeadBucketExtraResponse { + InternalBucketInfo extra_info = 1; +} + +message QueryIsPriceChangedRequest { + string bucket_name = 1; +} + +message QueryIsPriceChangedResponse { + bool changed = 1; + string current_read_price = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string current_primary_store_price = 3 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string current_secondary_store_price = 4 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string current_validator_tax_rate = 5 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string new_read_price = 6 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string new_primary_store_price = 7 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string new_secondary_store_price = 8 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string new_validator_tax_rate = 9 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} // this line is used by starport scaffolding # 3 diff --git a/sdk/client/gnfd_tm.go b/sdk/client/gnfd_tm.go index 429554cd7..a3558e039 100644 --- a/sdk/client/gnfd_tm.go +++ b/sdk/client/gnfd_tm.go @@ -4,9 +4,8 @@ import ( "context" "encoding/hex" - "github.com/cometbft/cometbft/votepool" - ctypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/cometbft/cometbft/votepool" ) // GetBlock by height, gets the latest block if height is nil diff --git a/sdk/client/gnfd_tm_test.go b/sdk/client/gnfd_tm_test.go index 08b8fad80..861f112ec 100644 --- a/sdk/client/gnfd_tm_test.go +++ b/sdk/client/gnfd_tm_test.go @@ -7,10 +7,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/assert" "github.com/bnb-chain/greenfield/sdk/client/test" "github.com/bnb-chain/greenfield/sdk/keys" - "github.com/stretchr/testify/assert" ) func TestTmClient(t *testing.T) { diff --git a/swagger/static/swagger.yaml b/swagger/static/swagger.yaml index f1e2a5a6a..8fd152810 100644 --- a/swagger/static/swagger.yaml +++ b/swagger/static/swagger.yaml @@ -2348,6 +2348,109 @@ paths: type: string tags: - Query + /greenfield/storage/head_bucket_extra/{bucket_name}: + get: + summary: >- + Queries a bucket extra info (with gvg bindings and price time) with + specify name. + operationId: HeadBucketExtra + responses: + '200': + description: A successful response. + schema: + type: object + properties: + extra_info: + type: object + properties: + price_time: + type: string + format: int64 + title: >- + the time of the payment price, used to calculate the + charge rate of the bucket + total_charge_size: + type: string + format: uint64 + title: >- + the total size of the objects in the bucket, used to + calculate the charge rate of the bucket + local_virtual_groups: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + description: id is the identifier of the local virtual group. + global_virtual_group_id: + type: integer + format: int64 + description: >- + global_virtual_group_id is the identifier of the + global virtual group. + stored_size: + type: string + format: uint64 + description: >- + stored_size is the size of the stored data in the + local virtual group. + total_charge_size: + type: string + format: uint64 + title: >- + total_charge_size is the total charged size of the + objects in the LVG. + + Notice that the minimum unit of charge is 128K + description: >- + Local virtual group(LVG) uniquely associated with a + global virtual group. + + Each bucket maintains a mapping from local virtual group + to global virtual group + + Each local virtual group is associated with a unique + virtual payment account, + + where all object fees are streamed to. + description: local_virtual_groups contains all the lvg of this bucket. + next_local_virtual_group_id: + type: integer + format: int64 + title: >- + next_local_virtual_group_id store the next id used by + local virtual group + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: bucket_name + in: path + required: true + type: string + tags: + - Query /greenfield/storage/head_bucket_nft/{token_id}: get: summary: Queries a bucket with EIP712 standard metadata info @@ -3041,6 +3144,63 @@ paths: type: string tags: - Query + /greenfield/storage/is_price_changed/{bucket_name}: + get: + summary: Queries whether read and storage prices changed for the bucket. + operationId: QueryIsPriceChanged + responses: + '200': + description: A successful response. + schema: + type: object + properties: + changed: + type: boolean + current_read_price: + type: string + current_primary_store_price: + type: string + current_secondary_store_price: + type: string + current_validator_tax_rate: + type: string + new_read_price: + type: string + new_primary_store_price: + type: string + new_secondary_store_price: + type: string + new_validator_tax_rate: + type: string + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: bucket_name + in: path + required: true + type: string + tags: + - Query /greenfield/storage/list_buckets: get: summary: Queries a list of bucket items. @@ -32860,6 +33020,99 @@ definitions: value: type: string title: attributes + greenfield.storage.InternalBucketInfo: + type: object + properties: + price_time: + type: string + format: int64 + title: >- + the time of the payment price, used to calculate the charge rate of + the bucket + total_charge_size: + type: string + format: uint64 + title: >- + the total size of the objects in the bucket, used to calculate the + charge rate of the bucket + local_virtual_groups: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + description: id is the identifier of the local virtual group. + global_virtual_group_id: + type: integer + format: int64 + description: >- + global_virtual_group_id is the identifier of the global virtual + group. + stored_size: + type: string + format: uint64 + description: >- + stored_size is the size of the stored data in the local virtual + group. + total_charge_size: + type: string + format: uint64 + title: >- + total_charge_size is the total charged size of the objects in + the LVG. + + Notice that the minimum unit of charge is 128K + description: >- + Local virtual group(LVG) uniquely associated with a global virtual + group. + + Each bucket maintains a mapping from local virtual group to global + virtual group + + Each local virtual group is associated with a unique virtual payment + account, + + where all object fees are streamed to. + description: local_virtual_groups contains all the lvg of this bucket. + next_local_virtual_group_id: + type: integer + format: int64 + title: >- + next_local_virtual_group_id store the next id used by local virtual + group + greenfield.storage.LocalVirtualGroup: + type: object + properties: + id: + type: integer + format: int64 + description: id is the identifier of the local virtual group. + global_virtual_group_id: + type: integer + format: int64 + description: global_virtual_group_id is the identifier of the global virtual group. + stored_size: + type: string + format: uint64 + description: stored_size is the size of the stored data in the local virtual group. + total_charge_size: + type: string + format: uint64 + title: |- + total_charge_size is the total charged size of the objects in the LVG. + Notice that the minimum unit of charge is 128K + description: >- + Local virtual group(LVG) uniquely associated with a global virtual group. + + Each bucket maintains a mapping from local virtual group to global virtual + group + + Each local virtual group is associated with a unique virtual payment + account, + + where all object fees are streamed to. greenfield.storage.ObjectInfo: type: object properties: @@ -33141,6 +33394,71 @@ definitions: value: type: string title: attributes + greenfield.storage.QueryHeadBucketExtraResponse: + type: object + properties: + extra_info: + type: object + properties: + price_time: + type: string + format: int64 + title: >- + the time of the payment price, used to calculate the charge rate + of the bucket + total_charge_size: + type: string + format: uint64 + title: >- + the total size of the objects in the bucket, used to calculate the + charge rate of the bucket + local_virtual_groups: + type: array + items: + type: object + properties: + id: + type: integer + format: int64 + description: id is the identifier of the local virtual group. + global_virtual_group_id: + type: integer + format: int64 + description: >- + global_virtual_group_id is the identifier of the global + virtual group. + stored_size: + type: string + format: uint64 + description: >- + stored_size is the size of the stored data in the local + virtual group. + total_charge_size: + type: string + format: uint64 + title: >- + total_charge_size is the total charged size of the objects + in the LVG. + + Notice that the minimum unit of charge is 128K + description: >- + Local virtual group(LVG) uniquely associated with a global + virtual group. + + Each bucket maintains a mapping from local virtual group to + global virtual group + + Each local virtual group is associated with a unique virtual + payment account, + + where all object fees are streamed to. + description: local_virtual_groups contains all the lvg of this bucket. + next_local_virtual_group_id: + type: integer + format: int64 + title: >- + next_local_virtual_group_id store the next id used by local + virtual group greenfield.storage.QueryHeadBucketResponse: type: object properties: @@ -33391,6 +33709,27 @@ definitions: objects of each bucket must be stored in a GVG within a group family. + greenfield.storage.QueryIsPriceChangedResponse: + type: object + properties: + changed: + type: boolean + current_read_price: + type: string + current_primary_store_price: + type: string + current_secondary_store_price: + type: string + current_validator_tax_rate: + type: string + new_read_price: + type: string + new_primary_store_price: + type: string + new_secondary_store_price: + type: string + new_validator_tax_rate: + type: string greenfield.storage.QueryListBucketsResponse: type: object properties: diff --git a/x/challenge/keeper/msg_server_attest.go b/x/challenge/keeper/msg_server_attest.go index 1386f880f..74b52918d 100644 --- a/x/challenge/keeper/msg_server_attest.go +++ b/x/challenge/keeper/msg_server_attest.go @@ -6,7 +6,6 @@ import ( "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" - storagetypes "github.com/bnb-chain/greenfield/x/storage/types" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" @@ -14,6 +13,7 @@ import ( "github.com/bnb-chain/greenfield/x/challenge/types" paymentmoduletypes "github.com/bnb-chain/greenfield/x/payment/types" sptypes "github.com/bnb-chain/greenfield/x/sp/types" + storagetypes "github.com/bnb-chain/greenfield/x/storage/types" ) // Attest handles user's request for attesting a challenge. diff --git a/x/payment/keeper/auto_resume_record.go b/x/payment/keeper/auto_resume_record.go index 2654e529e..961013e58 100644 --- a/x/payment/keeper/auto_resume_record.go +++ b/x/payment/keeper/auto_resume_record.go @@ -66,7 +66,7 @@ func (k Keeper) ExistsAutoResumeRecord( exists := false for ; iterator.Valid(); iterator.Next() { record := types.ParseAutoResumeRecordKey(iterator.Key()) - if record.Timestamp > timestamp { + if timestamp > 0 && record.Timestamp > timestamp { break } if sdk.MustAccAddressFromHex(record.Addr).Equals(addr) { diff --git a/x/payment/keeper/msg_server_withdraw.go b/x/payment/keeper/msg_server_withdraw.go index 8d8cec396..cf2ff42f8 100644 --- a/x/payment/keeper/msg_server_withdraw.go +++ b/x/payment/keeper/msg_server_withdraw.go @@ -17,6 +17,10 @@ func (k msgServer) Withdraw(goCtx context.Context, msg *types.MsgWithdraw) (*typ if !found { return nil, types.ErrStreamRecordNotFound } + // check status + if streamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN { + return nil, errors.Wrapf(types.ErrInvalidStreamAccountStatus, "stream record is frozen") + } // check whether creator can withdraw if msg.Creator != msg.From { paymentAccount, found := k.Keeper.GetPaymentAccount(ctx, from) diff --git a/x/payment/keeper/out_flow.go b/x/payment/keeper/out_flow.go index ab76aec05..0049babfb 100644 --- a/x/payment/keeper/out_flow.go +++ b/x/payment/keeper/out_flow.go @@ -65,10 +65,19 @@ func (k Keeper) DeleteOutFlow(ctx sdk.Context, key []byte) { // MergeActiveOutFlows merge active OutFlows and save them in the store func (k Keeper) MergeActiveOutFlows(ctx sdk.Context, addr sdk.AccAddress, outFlows []types.OutFlow) int { + return k.mergeOutFlows(ctx, addr, types.OUT_FLOW_STATUS_ACTIVE, outFlows) +} + +// MergeFrozenOutFlows merge frozen OutFlows and save them in the store +func (k Keeper) MergeFrozenOutFlows(ctx sdk.Context, addr sdk.AccAddress, outFlows []types.OutFlow) int { + return k.mergeOutFlows(ctx, addr, types.OUT_FLOW_STATUS_FROZEN, outFlows) +} + +func (k Keeper) mergeOutFlows(ctx sdk.Context, addr sdk.AccAddress, status types.OutFlowStatus, outFlows []types.OutFlow) int { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.OutFlowKeyPrefix) deltaCount := 0 for _, outFlow := range outFlows { - outFlow.Status = types.OUT_FLOW_STATUS_ACTIVE + outFlow.Status = status key := types.OutFlowKey(addr, outFlow.Status, sdk.MustAccAddressFromHex(outFlow.ToAddress)) value := store.Get(key) if value == nil { diff --git a/x/payment/keeper/storage_fee_charge.go b/x/payment/keeper/storage_fee_charge.go index da215a57e..c5d3239df 100644 --- a/x/payment/keeper/storage_fee_charge.go +++ b/x/payment/keeper/storage_fee_charge.go @@ -52,47 +52,106 @@ func (k Keeper) ApplyStreamRecordChanges(ctx sdk.Context, streamRecordChanges [] func (k Keeper) ApplyUserFlowsList(ctx sdk.Context, userFlowsList []types.UserFlows) (err error) { userFlowsList = k.MergeUserFlows(userFlowsList) currentTime := ctx.BlockTime().Unix() - var streamRecordChanges []types.StreamRecordChange + for _, userFlows := range userFlowsList { from := userFlows.From streamRecord, found := k.GetStreamRecord(ctx, from) if !found { streamRecord = types.NewStreamRecord(from, currentTime) } - // calculate rate changes in flowChanges - totalRate := sdk.ZeroInt() - for _, flowChange := range userFlows.Flows { - streamRecordChanges = append(streamRecordChanges, *types.NewDefaultStreamRecordChangeWithAddr(sdk.MustAccAddressFromHex(flowChange.ToAddress)).WithRateChange(flowChange.Rate)) - totalRate = totalRate.Add(flowChange.Rate) - } - streamRecordChange := types.NewDefaultStreamRecordChangeWithAddr(from).WithRateChange(totalRate.Neg()) - // storage fee preview - if ctx.IsCheckTx() { - reserveTime := k.GetParams(ctx).VersionedParams.ReserveTime - changeRate := totalRate.Neg() - event := &types.EventFeePreview{ - Account: from.String(), - Amount: changeRate.Mul(sdkmath.NewIntFromUint64(reserveTime)).Abs(), + if streamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE { + err = k.applyActiveUerFlows(ctx, userFlows, from, streamRecord) + if err != nil { + return err } - if changeRate.IsPositive() { - event.FeePreviewType = types.FEE_PREVIEW_TYPE_UNLOCKED_FEE - } else { - event.FeePreviewType = types.FEE_PREVIEW_TYPE_PRELOCKED_FEE + } else { // frozen status, should be called in end block for stop serving + err = k.applyFrozenUserFlows(ctx, userFlows, from, streamRecord) + if err != nil { + return err } - _ = ctx.EventManager().EmitTypedEvents(event) } - err = k.UpdateStreamRecord(ctx, streamRecord, streamRecordChange) - if err != nil { - return fmt.Errorf("apply stream record changes for user failed: %w", err) + } + return nil +} + +func (k Keeper) applyActiveUerFlows(ctx sdk.Context, userFlows types.UserFlows, from sdk.AccAddress, streamRecord *types.StreamRecord) error { + var rateChanges []types.StreamRecordChange + totalRate := sdk.ZeroInt() + for _, flowChange := range userFlows.Flows { + rateChanges = append(rateChanges, *types.NewDefaultStreamRecordChangeWithAddr(sdk.MustAccAddressFromHex(flowChange.ToAddress)).WithRateChange(flowChange.Rate)) + totalRate = totalRate.Add(flowChange.Rate) + } + streamRecordChange := types.NewDefaultStreamRecordChangeWithAddr(from).WithRateChange(totalRate.Neg()) + // storage fee preview + if ctx.IsCheckTx() { + reserveTime := k.GetParams(ctx).VersionedParams.ReserveTime + changeRate := totalRate.Neg() + event := &types.EventFeePreview{ + Account: from.String(), + Amount: changeRate.Mul(sdkmath.NewIntFromUint64(reserveTime)).Abs(), + } + if changeRate.IsPositive() { + event.FeePreviewType = types.FEE_PREVIEW_TYPE_UNLOCKED_FEE + } else { + event.FeePreviewType = types.FEE_PREVIEW_TYPE_PRELOCKED_FEE } + _ = ctx.EventManager().EmitTypedEvents(event) + } + err := k.UpdateStreamRecord(ctx, streamRecord, streamRecordChange) + if err != nil { + return fmt.Errorf("apply stream record changes for user failed: %w", err) + } - // update flows - deltaFlowCount := k.MergeActiveOutFlows(ctx, from, userFlows.Flows) // deltaFlowCount can be negative - streamRecord.OutFlowCount = uint64(int64(streamRecord.OutFlowCount) + int64(deltaFlowCount)) + // update flows + deltaFlowCount := k.MergeActiveOutFlows(ctx, from, userFlows.Flows) // deltaFlowCount can be negative + streamRecord.OutFlowCount = uint64(int64(streamRecord.OutFlowCount) + int64(deltaFlowCount)) - k.SetStreamRecord(ctx, streamRecord) + k.SetStreamRecord(ctx, streamRecord) + err = k.ApplyStreamRecordChanges(ctx, rateChanges) + if err != nil { + return fmt.Errorf("apply stream record changes failed: %w", err) } - err = k.ApplyStreamRecordChanges(ctx, streamRecordChanges) + return nil +} + +func (k Keeper) applyFrozenUserFlows(ctx sdk.Context, userFlows types.UserFlows, from sdk.AccAddress, streamRecord *types.StreamRecord) error { + forced, _ := ctx.Value(types.ForceUpdateStreamRecordKey).(bool) + if !forced { + return fmt.Errorf("stream record %s is frozen", streamRecord.Account) + } + + // the stream record could be totally frozen, or in the process of resuming + var activeOutFlows, frozenOutFlows []types.OutFlow + var activeRateChanges []types.StreamRecordChange + //var frozenRateChanges []types.StreamRecordChange + totalActiveRate, totalFrozenRate := sdk.ZeroInt(), sdk.ZeroInt() + for _, flowChange := range userFlows.Flows { + outFlow := k.GetOutFlow(ctx, sdk.MustAccAddressFromHex(streamRecord.Account), types.OUT_FLOW_STATUS_FROZEN, sdk.MustAccAddressFromHex(flowChange.ToAddress)) + if outFlow != nil { + frozenOutFlows = append(frozenOutFlows, flowChange) + //frozenRateChanges = append(frozenRateChanges, *types.NewDefaultStreamRecordChangeWithAddr(sdk.MustAccAddressFromHex(flowChange.ToAddress)).WithFrozenRateChange(flowChange.Rate)) + totalFrozenRate = totalFrozenRate.Add(flowChange.Rate) + } else { + activeOutFlows = append(activeOutFlows, flowChange) + activeRateChanges = append(activeRateChanges, *types.NewDefaultStreamRecordChangeWithAddr(sdk.MustAccAddressFromHex(flowChange.ToAddress)).WithRateChange(flowChange.Rate)) + totalActiveRate = totalActiveRate.Add(flowChange.Rate) + } + } + streamRecordChange := types.NewDefaultStreamRecordChangeWithAddr(from). + WithRateChange(totalActiveRate.Neg()).WithFrozenRateChange(totalFrozenRate.Neg()) + err := k.UpdateFrozenStreamRecord(ctx, streamRecord, streamRecordChange) + if err != nil { + return fmt.Errorf("apply stream record changes for user failed: %w", err) + } + + // update flows + deltaActiveFlowCount := k.MergeActiveOutFlows(ctx, from, activeOutFlows) // can be negative + deltaFrozenFlowCount := k.MergeFrozenOutFlows(ctx, from, frozenOutFlows) // can be negative + streamRecord.OutFlowCount = uint64(int64(streamRecord.OutFlowCount) + int64(deltaActiveFlowCount) + int64(deltaFrozenFlowCount)) + + k.SetStreamRecord(ctx, streamRecord) + //only apply activeRateChanges, for frozen rate changes, the out flow to gvg & gvg family had been deducted when settling + err = k.ApplyStreamRecordChanges(ctx, activeRateChanges) if err != nil { return fmt.Errorf("apply stream record changes failed: %w", err) } diff --git a/x/payment/keeper/storage_fee_charge_test.go b/x/payment/keeper/storage_fee_charge_test.go index 13d2a85b4..7faf16db7 100644 --- a/x/payment/keeper/storage_fee_charge_test.go +++ b/x/payment/keeper/storage_fee_charge_test.go @@ -25,6 +25,15 @@ func TestApplyFlowChanges(t *testing.T) { *types.NewDefaultStreamRecordChangeWithAddr(user).WithStaticBalanceChange(userInitBalance).WithRateChange(rate.Neg()), *types.NewDefaultStreamRecordChangeWithAddr(sp).WithRateChange(rate), } + sr := &types.StreamRecord{Account: user.String(), + OutFlowCount: 1, + StaticBalance: sdkmath.ZeroInt(), + BufferBalance: sdkmath.ZeroInt(), + LockBalance: sdkmath.ZeroInt(), + NetflowRate: sdkmath.ZeroInt(), + FrozenNetflowRate: sdkmath.ZeroInt(), + } + keeper.SetStreamRecord(ctx, sr) err := keeper.ApplyStreamRecordChanges(ctx, flowChanges) require.NoError(t, err) userStreamRecord, found := keeper.GetStreamRecord(ctx, user) @@ -47,6 +56,15 @@ func TestSettleStreamRecord(t *testing.T) { rate := sdkmath.NewInt(-100) staticBalance := sdkmath.NewInt(1e10) change := types.NewDefaultStreamRecordChangeWithAddr(user).WithRateChange(rate).WithStaticBalanceChange(staticBalance) + sr := &types.StreamRecord{Account: user.String(), + OutFlowCount: 1, + StaticBalance: sdkmath.ZeroInt(), + BufferBalance: sdkmath.ZeroInt(), + LockBalance: sdkmath.ZeroInt(), + NetflowRate: sdkmath.ZeroInt(), + FrozenNetflowRate: sdkmath.ZeroInt(), + } + keeper.SetStreamRecord(ctx, sr) _, err := keeper.UpdateStreamRecordByAddr(ctx, change) require.NoError(t, err) // check @@ -166,6 +184,7 @@ func TestAutoForceSettle(t *testing.T) { usrBeforeForceSettle, _ := keeper.GetStreamRecord(ctx, user) t.Logf("usrBeforeForceSettle: %s", usrBeforeForceSettle) + ctx = ctx.WithValue(types.ForceUpdateStreamRecordKey, true) time.Sleep(1 * time.Second) keeper.AutoSettle(ctx) diff --git a/x/payment/keeper/stream_record.go b/x/payment/keeper/stream_record.go index 5765af0d4..a965f44e9 100644 --- a/x/payment/keeper/stream_record.go +++ b/x/payment/keeper/stream_record.go @@ -6,6 +6,7 @@ import ( sdkmath "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/store/prefix" storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/bnb-chain/greenfield/x/payment/types" @@ -27,12 +28,18 @@ func (k Keeper) CheckStreamRecord(streamRecord *types.StreamRecord) { if streamRecord.NetflowRate.IsNil() { panic(fmt.Sprintf("invalid streamRecord netflowRate %s", streamRecord.NetflowRate)) } + if !streamRecord.FrozenNetflowRate.IsNil() && streamRecord.FrozenNetflowRate.IsPositive() { + panic(fmt.Sprintf("invalid streamRecord frozenNetflowRate %s", streamRecord.NetflowRate)) + } if streamRecord.LockBalance.IsNil() || streamRecord.LockBalance.IsNegative() { panic(fmt.Sprintf("invalid streamRecord lockBalance %s", streamRecord.LockBalance)) } if streamRecord.BufferBalance.IsNil() || streamRecord.BufferBalance.IsNegative() { panic(fmt.Sprintf("invalid streamRecord bufferBalance %s", streamRecord.BufferBalance)) } + if streamRecord.NetflowRate.IsNegative() && streamRecord.OutFlowCount == 0 { + panic(fmt.Sprintf("invalid streamRecord netflowRate %s when outFlowCount is zero", streamRecord.NetflowRate)) + } } // SetStreamRecord set a specific streamRecord in the store from its index @@ -107,8 +114,6 @@ func (k Keeper) GetAllStreamRecord(ctx sdk.Context) (list []types.StreamRecord) // it only handles the lock balance change and ignore the other changes(since the streams are already changed and the // accumulated OutFlows are changed outside this function) func (k Keeper) UpdateFrozenStreamRecord(ctx sdk.Context, streamRecord *types.StreamRecord, change *types.StreamRecordChange) error { - currentTimestamp := ctx.BlockTime().Unix() - streamRecord.CrudTimestamp = currentTimestamp // update lock balance if !change.LockBalanceChange.IsZero() { streamRecord.LockBalance = streamRecord.LockBalance.Add(change.LockBalanceChange) @@ -117,18 +122,17 @@ func (k Keeper) UpdateFrozenStreamRecord(ctx sdk.Context, streamRecord *types.St return fmt.Errorf("lock balance can not become negative, current: %s", streamRecord.LockBalance) } } + if !change.RateChange.IsZero() { + streamRecord.NetflowRate = streamRecord.NetflowRate.Add(change.RateChange) + } + if !change.FrozenRateChange.IsZero() { + streamRecord.FrozenNetflowRate = streamRecord.FrozenNetflowRate.Add(change.FrozenRateChange) + } return nil } func (k Keeper) UpdateStreamRecord(ctx sdk.Context, streamRecord *types.StreamRecord, change *types.StreamRecordChange) error { forced, _ := ctx.Value(types.ForceUpdateStreamRecordKey).(bool) // force update in end block - if streamRecord.Status != types.STREAM_ACCOUNT_STATUS_ACTIVE { - if forced { //stream record is frozen - return k.UpdateFrozenStreamRecord(ctx, streamRecord, change) - } - return fmt.Errorf("stream account %s is frozen", streamRecord.Account) - } - isPay := change.StaticBalanceChange.IsNegative() || change.RateChange.IsNegative() currentTimestamp := ctx.BlockTime().Unix() timestamp := streamRecord.CrudTimestamp @@ -150,16 +154,15 @@ func (k Keeper) UpdateStreamRecord(ctx sdk.Context, streamRecord *types.StreamRe } } // update buffer balance - if !change.RateChange.IsZero() { - streamRecord.NetflowRate = streamRecord.NetflowRate.Add(change.RateChange) - newBufferBalance := sdkmath.ZeroInt() - if streamRecord.NetflowRate.IsNegative() { - newBufferBalance = streamRecord.NetflowRate.Abs().Mul(sdkmath.NewIntFromUint64(params.VersionedParams.ReserveTime)) - } - if !newBufferBalance.Equal(streamRecord.BufferBalance) { - streamRecord.StaticBalance = streamRecord.StaticBalance.Sub(newBufferBalance).Add(streamRecord.BufferBalance) - streamRecord.BufferBalance = newBufferBalance - } + // because reserve time could be changed, so we need to re-calculate buffer balance even rate change is zero + streamRecord.NetflowRate = streamRecord.NetflowRate.Add(change.RateChange) + newBufferBalance := sdkmath.ZeroInt() + if streamRecord.NetflowRate.IsNegative() { + newBufferBalance = streamRecord.NetflowRate.Abs().Mul(sdkmath.NewIntFromUint64(params.VersionedParams.ReserveTime)) + } + if !newBufferBalance.Equal(streamRecord.BufferBalance) { + streamRecord.StaticBalance = streamRecord.StaticBalance.Sub(newBufferBalance).Add(streamRecord.BufferBalance) + streamRecord.BufferBalance = newBufferBalance } // update static balance if !change.StaticBalanceChange.IsZero() { @@ -178,7 +181,7 @@ func (k Keeper) UpdateStreamRecord(ctx sdk.Context, streamRecord *types.StreamRe } } } - // if the change is a pay(which decreases the static balance or netflow rate), the left static balance should be enough + // if the change is a pay (which decreases the static balance or netflow rate), the left static balance should be enough if !forced && isPay && streamRecord.StaticBalance.IsNegative() { return fmt.Errorf("stream account %s balance not enough, lack of %s BNB", streamRecord.Account, streamRecord.StaticBalance.Abs()) } @@ -188,57 +191,17 @@ func (k Keeper) UpdateStreamRecord(ctx sdk.Context, streamRecord *types.StreamRe payDuration := streamRecord.StaticBalance.Add(streamRecord.BufferBalance).Quo(streamRecord.NetflowRate.Abs()) if payDuration.LTE(sdkmath.NewIntFromUint64(params.ForcedSettleTime)) { if !forced { - return fmt.Errorf("stream account %s balance not enough, lack of %s BNB", streamRecord.Account, streamRecord.StaticBalance.Abs()) - } - } - settleTimestamp = currentTimestamp - int64(params.ForcedSettleTime) + payDuration.Int64() - } - k.UpdateAutoSettleRecord(ctx, sdk.MustAccAddressFromHex(streamRecord.Account), streamRecord.SettleTimestamp, settleTimestamp) - streamRecord.SettleTimestamp = settleTimestamp - return nil -} - -func (k Keeper) SettleStreamRecord(ctx sdk.Context, streamRecord *types.StreamRecord) error { - currentTimestamp := ctx.BlockTime().Unix() - crudTimestamp := streamRecord.CrudTimestamp - params := k.GetParams(ctx) - - if currentTimestamp != crudTimestamp { - if !streamRecord.NetflowRate.IsZero() { - flowDelta := streamRecord.NetflowRate.MulRaw(currentTimestamp - crudTimestamp) - streamRecord.StaticBalance = streamRecord.StaticBalance.Add(flowDelta) - } - streamRecord.CrudTimestamp = currentTimestamp - } - - if streamRecord.StaticBalance.IsNegative() { - account := sdk.MustAccAddressFromHex(streamRecord.Account) - hasBankAccount := k.accountKeeper.HasAccount(ctx, account) - if hasBankAccount { - coins := sdk.NewCoins(sdk.NewCoin(params.FeeDenom, streamRecord.StaticBalance.Abs())) - err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, account, types.ModuleName, coins) - if err != nil { - ctx.Logger().Info("auto transfer failed when settling", "account", streamRecord.Account, "err", err, "coins", coins) - } else { - streamRecord.StaticBalance = sdkmath.ZeroInt() + return fmt.Errorf("stream account %s lacks of %s BNB", streamRecord.Account, streamRecord.StaticBalance.Abs()) } - } - } - - if streamRecord.NetflowRate.IsNegative() { - payDuration := streamRecord.StaticBalance.Add(streamRecord.BufferBalance).Quo(streamRecord.NetflowRate.Abs()) - if payDuration.LTE(sdkmath.NewIntFromUint64(params.ForcedSettleTime)) { err := k.ForceSettle(ctx, streamRecord) if err != nil { - return err + return fmt.Errorf("check and force settle failed, err: %w", err) } - } else { - settleTimestamp := currentTimestamp - int64(params.ForcedSettleTime) + payDuration.Int64() - k.UpdateAutoSettleRecord(ctx, sdk.MustAccAddressFromHex(streamRecord.Account), streamRecord.SettleTimestamp, settleTimestamp) - streamRecord.SettleTimestamp = settleTimestamp } + settleTimestamp = currentTimestamp - int64(params.ForcedSettleTime) + payDuration.Int64() } - + k.UpdateAutoSettleRecord(ctx, sdk.MustAccAddressFromHex(streamRecord.Account), streamRecord.SettleTimestamp, settleTimestamp) + streamRecord.SettleTimestamp = settleTimestamp return nil } @@ -258,6 +221,7 @@ func (k Keeper) ForceSettle(ctx sdk.Context, streamRecord *types.StreamRecord) e change := types.NewDefaultStreamRecordChangeWithAddr(types.GovernanceAddress).WithStaticBalanceChange(totalBalance) _, err := k.UpdateStreamRecordByAddr(ctx, change) if err != nil { + telemetry.IncrCounter(1, types.GovernanceAddressLackBalanceLabel) return fmt.Errorf("update governance stream record failed: %w", err) } } @@ -298,7 +262,7 @@ func (k Keeper) AutoSettle(ctx sdk.Context) { if streamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE { count++ // add one for a stream record - err := k.SettleStreamRecord(ctx, streamRecord) + err := k.UpdateStreamRecord(ctx, streamRecord, types.NewDefaultStreamRecordChangeWithAddr(addr)) if err != nil { ctx.Logger().Error("auto settle, settle stream record failed", "err", err.Error()) continue @@ -348,6 +312,7 @@ func (k Keeper) AutoSettle(ctx sdk.Context) { _, err := k.UpdateStreamRecordByAddr(ctx, flowChange) if err != nil { ctx.Logger().Error("auto settle, update stream record failed", "address", outFlow.ToAddress, "rate", outFlow.Rate.Neg()) + panic("should not happen") } flowStore.Delete(flowIterator.Key()) @@ -369,6 +334,7 @@ func (k Keeper) AutoSettle(ctx sdk.Context) { if !flowIterator.Valid() || finished { if !streamRecord.NetflowRate.IsZero() { ctx.Logger().Error("should not happen, stream netflow rate is not zero", "address", streamRecord.Account) + panic("should not happen") } k.RemoveAutoSettleRecord(ctx, record.Timestamp, addr) } @@ -382,8 +348,9 @@ func (k Keeper) TryResumeStreamRecord(ctx sdk.Context, streamRecord *types.Strea return fmt.Errorf("stream account %s status is not frozen", streamRecord.Account) } - if !streamRecord.NetflowRate.IsNil() && !streamRecord.NetflowRate.IsZero() { // the account is resuming - return fmt.Errorf("stream account %s status is resuming, although it is frozen now", streamRecord.Account) + exists := k.ExistsAutoResumeRecord(ctx, 0, sdk.MustAccAddressFromHex(streamRecord.Account)) + if exists { + return fmt.Errorf("stream account %s status is resuming, please wait", streamRecord.Account) } params := k.GetParams(ctx) @@ -501,7 +468,7 @@ func (k Keeper) AutoResume(ctx sdk.Context) { _, err := k.UpdateStreamRecordByAddr(ctx, flowChange) if err != nil { ctx.Logger().Error("auto resume, update receiver stream record failed", "address", outFlow.ToAddress, "err", err.Error()) - break + panic("should not happen") } flowStore.Delete(flowIterator.Key()) @@ -522,12 +489,14 @@ func (k Keeper) AutoResume(ctx sdk.Context) { if !flowIterator.Valid() || finished { if !streamRecord.FrozenNetflowRate.IsZero() { ctx.Logger().Error("should not happen, stream frozen netflow rate is not zero", "address", streamRecord.Account) + panic("should not happen") } streamRecord.Status = types.STREAM_ACCOUNT_STATUS_ACTIVE change := types.NewDefaultStreamRecordChangeWithAddr(addr) err := k.UpdateStreamRecord(ctx, streamRecord, change) if err != nil { ctx.Logger().Error("auto resume, update stream record failed", "err", err.Error()) + panic("should not happen") } k.RemoveAutoResumeRecord(ctx, record.Timestamp, addr) } diff --git a/x/payment/keeper/stream_record_test.go b/x/payment/keeper/stream_record_test.go index 5fddae9ae..a726888d4 100644 --- a/x/payment/keeper/stream_record_test.go +++ b/x/payment/keeper/stream_record_test.go @@ -14,18 +14,26 @@ import ( "github.com/bnb-chain/greenfield/x/payment/types" ) -func TestTryResumeStreamRecord_InResuming(t *testing.T) { +func TestTryResumeStreamRecord_InResumingOrSettling(t *testing.T) { keeper, ctx, _ := makePaymentKeeper(t) ctx = ctx.WithBlockTime(time.Now()) - // further deposit to a resuming account is not allowed + account := sample.RandAccAddress() + // deposit to a resuming account is not allowed streamRecord := &types.StreamRecord{ + Account: account.String(), Status: types.STREAM_ACCOUNT_STATUS_FROZEN, NetflowRate: sdkmath.NewInt(-100), } + + keeper.SetAutoResumeRecord(ctx, &types.AutoResumeRecord{ + Timestamp: ctx.BlockTime().Unix() + 10, + Addr: account.String(), + }) + deposit := sdkmath.NewInt(100) err := keeper.TryResumeStreamRecord(ctx, streamRecord, deposit) - require.ErrorContains(t, err, "resuming") + require.ErrorContains(t, err, "is resuming") } func TestTryResumeStreamRecord_ResumeInOneBlock(t *testing.T) { @@ -122,6 +130,109 @@ func TestTryResumeStreamRecord_ResumeInMultipleBlocks(t *testing.T) { } keeper.SetOutFlow(ctx, user, outFlow3) + // try to resume stream record + err := keeper.TryResumeStreamRecord(ctx, streamRecord, rate.SubRaw(10).MulRaw(int64(params.VersionedParams.ReserveTime))) + require.NoError(t, err) //only added static balance + found := keeper.ExistsAutoResumeRecord(ctx, ctx.BlockTime().Unix(), user) + require.True(t, !found) + streamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, streamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + + err = keeper.TryResumeStreamRecord(ctx, streamRecord, rate.MulRaw(int64(params.VersionedParams.ReserveTime))) + require.NoError(t, err) + + // still frozen + userStreamRecord, _ := keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + + _, found = keeper.GetAutoResumeRecord(ctx, ctx.BlockTime().Unix(), user) + require.True(t, found) + + // resume in end block + keeper.AutoResume(ctx) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + + // resume in end block + keeper.AutoResume(ctx) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + + // resume in end block + keeper.AutoResume(ctx) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, userStreamRecord.NetflowRate, rate.Neg()) + require.Equal(t, userStreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) + + gvg1StreamRecord, _ := keeper.GetStreamRecord(ctx, gvg1) + require.True(t, gvg1StreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, gvg1StreamRecord.NetflowRate, gvg1Rate) + require.Equal(t, gvg1StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) + + gvg2StreamRecord, _ := keeper.GetStreamRecord(ctx, gvg2) + require.True(t, gvg2StreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, gvg2StreamRecord.NetflowRate, gvg2Rate) + require.Equal(t, gvg2StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) + + gvg3StreamRecord, _ := keeper.GetStreamRecord(ctx, gvg3) + require.True(t, gvg3StreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, gvg3StreamRecord.NetflowRate, gvg3Rate) + require.Equal(t, gvg3StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) +} + +func TestTryResumeStreamRecord_ResumeInMultipleBlocks_BalanceNotEnoughFinally(t *testing.T) { + keeper, ctx, depKeepers := makePaymentKeeper(t) + ctx = ctx.WithBlockTime(time.Now()) + + // resume account in multiple blocks + params := keeper.GetParams(ctx) + params.MaxAutoResumeFlowCount = 1 + _ = keeper.SetParams(ctx, params) + + rate := sdkmath.NewInt(300) + user := sample.RandAccAddress() + streamRecord := &types.StreamRecord{ + StaticBalance: sdkmath.ZeroInt(), + BufferBalance: sdkmath.ZeroInt(), + LockBalance: sdkmath.ZeroInt(), + Account: user.String(), + Status: types.STREAM_ACCOUNT_STATUS_FROZEN, + NetflowRate: sdkmath.NewInt(0), + FrozenNetflowRate: rate.Neg(), + OutFlowCount: 3, + } + keeper.SetStreamRecord(ctx, streamRecord) + + gvgAddress := []sdk.AccAddress{sample.RandAccAddress(), sample.RandAccAddress(), sample.RandAccAddress()} + + gvg1 := gvgAddress[0] + gvg1Rate := sdk.NewInt(50) + outFlow1 := &types.OutFlow{ + ToAddress: gvg1.String(), + Rate: gvg1Rate, + Status: types.OUT_FLOW_STATUS_FROZEN, + } + keeper.SetOutFlow(ctx, user, outFlow1) + + gvg2 := gvgAddress[1] + gvg2Rate := sdk.NewInt(100) + outFlow2 := &types.OutFlow{ + ToAddress: gvg2.String(), + Rate: gvg2Rate, + Status: types.OUT_FLOW_STATUS_FROZEN, + } + keeper.SetOutFlow(ctx, user, outFlow2) + + gvg3 := gvgAddress[2] + gvg3Rate := sdk.NewInt(150) + outFlow3 := &types.OutFlow{ + ToAddress: gvg3.String(), + Rate: gvg3Rate, + Status: types.OUT_FLOW_STATUS_FROZEN, + } + keeper.SetOutFlow(ctx, user, outFlow3) + err := keeper.TryResumeStreamRecord(ctx, streamRecord, rate.MulRaw(int64(params.VersionedParams.ReserveTime))) require.NoError(t, err) @@ -142,7 +253,137 @@ func TestTryResumeStreamRecord_ResumeInMultipleBlocks(t *testing.T) { userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + // time flies + timestamp := ctx.BlockTime().Unix() + int64(params.VersionedParams.ReserveTime)*2 + ctx = ctx.WithBlockTime(time.Unix(timestamp, 0)) + + depKeepers.AccountKeeper.EXPECT().HasAccount(gomock.Any(), gomock.Any()). + Return(true).AnyTimes() + depKeepers.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("fail to transfer")).AnyTimes() + // resume in end block + ctx = ctx.WithValue(types.ForceUpdateStreamRecordKey, true) + + keeper.AutoResume(ctx) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + require.Equal(t, userStreamRecord.NetflowRate, rate.Neg()) + require.Equal(t, userStreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) + + gvg1StreamRecord, _ := keeper.GetStreamRecord(ctx, gvg1) + require.True(t, gvg1StreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, gvg1StreamRecord.NetflowRate, gvg1Rate) + require.Equal(t, gvg1StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) + + gvg2StreamRecord, _ := keeper.GetStreamRecord(ctx, gvg2) + require.True(t, gvg2StreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, gvg2StreamRecord.NetflowRate, gvg2Rate) + require.Equal(t, gvg2StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) + + gvg3StreamRecord, _ := keeper.GetStreamRecord(ctx, gvg3) + require.True(t, gvg3StreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, gvg3StreamRecord.NetflowRate, gvg3Rate) + require.Equal(t, gvg3StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) + + // there will be an auto settle record + autoSettles := keeper.GetAllAutoSettleRecord(ctx) + found = false + for _, settle := range autoSettles { + if settle.GetAddr() == user.String() { + found = true + } + } + require.True(t, found, "") + + keeper.AutoSettle(ctx) + keeper.AutoSettle(ctx) + keeper.AutoSettle(ctx) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + require.Equal(t, userStreamRecord.NetflowRate, sdkmath.ZeroInt()) + require.Equal(t, userStreamRecord.FrozenNetflowRate, rate.Neg()) +} + +func TestAutoSettle_AccountIsInResuming(t *testing.T) { + keeper, ctx, _ := makePaymentKeeper(t) + ctx = ctx.WithBlockTime(time.Now()) + + // resume account in multiple blocks + params := keeper.GetParams(ctx) + params.MaxAutoResumeFlowCount = 1 + _ = keeper.SetParams(ctx, params) + + rate := sdkmath.NewInt(300) + user := sample.RandAccAddress() + streamRecord := &types.StreamRecord{ + StaticBalance: sdkmath.ZeroInt(), + BufferBalance: sdkmath.ZeroInt(), + LockBalance: sdkmath.ZeroInt(), + Account: user.String(), + Status: types.STREAM_ACCOUNT_STATUS_FROZEN, + NetflowRate: sdkmath.NewInt(0), + FrozenNetflowRate: rate.Neg(), + OutFlowCount: 3, + } + keeper.SetStreamRecord(ctx, streamRecord) + + gvgAddress := []sdk.AccAddress{sample.RandAccAddress(), sample.RandAccAddress(), sample.RandAccAddress()} + + gvg1 := gvgAddress[0] + gvg1Rate := sdk.NewInt(50) + outFlow1 := &types.OutFlow{ + ToAddress: gvg1.String(), + Rate: gvg1Rate, + Status: types.OUT_FLOW_STATUS_FROZEN, + } + keeper.SetOutFlow(ctx, user, outFlow1) + + gvg2 := gvgAddress[1] + gvg2Rate := sdk.NewInt(100) + outFlow2 := &types.OutFlow{ + ToAddress: gvg2.String(), + Rate: gvg2Rate, + Status: types.OUT_FLOW_STATUS_FROZEN, + } + keeper.SetOutFlow(ctx, user, outFlow2) + + gvg3 := gvgAddress[2] + gvg3Rate := sdk.NewInt(150) + outFlow3 := &types.OutFlow{ + ToAddress: gvg3.String(), + Rate: gvg3Rate, + Status: types.OUT_FLOW_STATUS_FROZEN, + } + keeper.SetOutFlow(ctx, user, outFlow3) + + err := keeper.TryResumeStreamRecord(ctx, streamRecord, rate.MulRaw(int64(params.VersionedParams.ReserveTime))) + require.NoError(t, err) + + // still frozen + userStreamRecord, _ := keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + + _, found := keeper.GetAutoResumeRecord(ctx, ctx.BlockTime().Unix(), user) + require.True(t, found) + + // add auto settle record + keeper.SetAutoSettleRecord(ctx, &types.AutoSettleRecord{ + Timestamp: ctx.BlockTime().Unix(), + Addr: user.String(), + }) + + keeper.AutoSettle(ctx) + keeper.AutoResume(ctx) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + + keeper.AutoSettle(ctx) + keeper.AutoResume(ctx) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + + keeper.AutoSettle(ctx) keeper.AutoResume(ctx) userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) @@ -165,7 +406,7 @@ func TestTryResumeStreamRecord_ResumeInMultipleBlocks(t *testing.T) { require.Equal(t, gvg3StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) } -func TestAutoSettle_FreezeInOneBlock(t *testing.T) { +func TestAutoSettle_SettleInOneBlock(t *testing.T) { keeper, ctx, depKeepers := makePaymentKeeper(t) ctx = ctx.WithBlockTime(time.Now()) @@ -196,7 +437,7 @@ func TestAutoSettle_FreezeInOneBlock(t *testing.T) { LockBalance: sdkmath.ZeroInt(), Account: gvg.String(), Status: types.STREAM_ACCOUNT_STATUS_ACTIVE, - NetflowRate: sdkmath.NewInt(0), + NetflowRate: rate, FrozenNetflowRate: sdkmath.NewInt(0), OutFlowCount: 0, } @@ -214,6 +455,7 @@ func TestAutoSettle_FreezeInOneBlock(t *testing.T) { Addr: user.String(), }) + ctx = ctx.WithValue(types.ForceUpdateStreamRecordKey, true) keeper.AutoSettle(ctx) userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) @@ -226,7 +468,7 @@ func TestAutoSettle_FreezeInOneBlock(t *testing.T) { require.Equal(t, gvgOutFlow.Rate, rate) } -func TestAutoSettle_FreezeInMultipleBlocks(t *testing.T) { +func TestAutoSettle_SettleInMultipleBlocks(t *testing.T) { keeper, ctx, depKeepers := makePaymentKeeper(t) ctx = ctx.WithBlockTime(time.Now()) @@ -263,7 +505,7 @@ func TestAutoSettle_FreezeInMultipleBlocks(t *testing.T) { LockBalance: sdkmath.ZeroInt(), Account: gvg1.String(), Status: types.STREAM_ACCOUNT_STATUS_ACTIVE, - NetflowRate: sdkmath.NewInt(0), + NetflowRate: sdkmath.NewInt(50), FrozenNetflowRate: sdkmath.NewInt(0), OutFlowCount: 0, } @@ -283,7 +525,7 @@ func TestAutoSettle_FreezeInMultipleBlocks(t *testing.T) { LockBalance: sdkmath.ZeroInt(), Account: gvg2.String(), Status: types.STREAM_ACCOUNT_STATUS_ACTIVE, - NetflowRate: sdkmath.NewInt(0), + NetflowRate: sdkmath.NewInt(100), FrozenNetflowRate: sdkmath.NewInt(0), OutFlowCount: 0, } @@ -303,7 +545,7 @@ func TestAutoSettle_FreezeInMultipleBlocks(t *testing.T) { LockBalance: sdkmath.ZeroInt(), Account: gvg3.String(), Status: types.STREAM_ACCOUNT_STATUS_ACTIVE, - NetflowRate: sdkmath.NewInt(0), + NetflowRate: sdkmath.NewInt(150), FrozenNetflowRate: sdkmath.NewInt(0), OutFlowCount: 0, } @@ -321,6 +563,7 @@ func TestAutoSettle_FreezeInMultipleBlocks(t *testing.T) { Addr: user.String(), }) + ctx = ctx.WithValue(types.ForceUpdateStreamRecordKey, true) keeper.AutoSettle(ctx) // this is for settle stream, it is counted userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) @@ -355,3 +598,157 @@ func TestAutoSettle_FreezeInMultipleBlocks(t *testing.T) { require.Equal(t, gvg3OutFlow.Status, types.OUT_FLOW_STATUS_FROZEN) require.Equal(t, gvg3OutFlow.Rate, sdkmath.NewInt(150)) } + +func TestAutoSettle_SettleInMultipleBlocks_AutoResumeExists(t *testing.T) { + keeper, ctx, depKeepers := makePaymentKeeper(t) + ctx = ctx.WithBlockTime(time.Now()) + + // freeze account in multiple blocks + params := keeper.GetParams(ctx) + params.MaxAutoSettleFlowCount = 1 + params.MaxAutoResumeFlowCount = 1 + _ = keeper.SetParams(ctx, params) + + depKeepers.AccountKeeper.EXPECT().HasAccount(gomock.Any(), gomock.Any()). + Return(true).AnyTimes() + depKeepers.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("fail to transfer")).AnyTimes() + + rate := sdkmath.NewInt(300) + user := sample.RandAccAddress() + userStreamRecord := &types.StreamRecord{ + StaticBalance: sdkmath.ZeroInt(), + BufferBalance: sdkmath.ZeroInt(), + LockBalance: sdkmath.ZeroInt(), + Account: user.String(), + Status: types.STREAM_ACCOUNT_STATUS_FROZEN, + NetflowRate: sdk.ZeroInt(), + FrozenNetflowRate: rate.Neg(), + OutFlowCount: 3, + } + keeper.SetStreamRecord(ctx, userStreamRecord) + + gvgAddress := []sdk.AccAddress{sample.RandAccAddress(), sample.RandAccAddress(), sample.RandAccAddress()} + + gvg1 := gvgAddress[0] + gvg1StreamRecord := &types.StreamRecord{ + StaticBalance: sdkmath.ZeroInt(), + BufferBalance: sdkmath.ZeroInt(), + LockBalance: sdkmath.ZeroInt(), + Account: gvg1.String(), + Status: types.STREAM_ACCOUNT_STATUS_ACTIVE, + NetflowRate: sdkmath.NewInt(0), + FrozenNetflowRate: sdkmath.NewInt(0), + OutFlowCount: 0, + } + keeper.SetStreamRecord(ctx, gvg1StreamRecord) + + outFlow1 := &types.OutFlow{ + ToAddress: gvg1.String(), + Rate: sdkmath.NewInt(50), + Status: types.OUT_FLOW_STATUS_FROZEN, + } + keeper.SetOutFlow(ctx, user, outFlow1) + + gvg2 := gvgAddress[1] + gvg2StreamRecord := &types.StreamRecord{ + StaticBalance: sdkmath.ZeroInt(), + BufferBalance: sdkmath.ZeroInt(), + LockBalance: sdkmath.ZeroInt(), + Account: gvg2.String(), + Status: types.STREAM_ACCOUNT_STATUS_ACTIVE, + NetflowRate: sdkmath.NewInt(0), + FrozenNetflowRate: sdkmath.NewInt(0), + OutFlowCount: 0, + } + keeper.SetStreamRecord(ctx, gvg2StreamRecord) + + outFlow2 := &types.OutFlow{ + ToAddress: gvg2.String(), + Rate: sdkmath.NewInt(100), + Status: types.OUT_FLOW_STATUS_FROZEN, + } + keeper.SetOutFlow(ctx, user, outFlow2) + + gvg3 := gvgAddress[2] + gvg3StreamRecord := &types.StreamRecord{ + StaticBalance: sdkmath.ZeroInt(), + BufferBalance: sdkmath.ZeroInt(), + LockBalance: sdkmath.ZeroInt(), + Account: gvg3.String(), + Status: types.STREAM_ACCOUNT_STATUS_ACTIVE, + NetflowRate: sdkmath.NewInt(0), + FrozenNetflowRate: sdkmath.NewInt(0), + OutFlowCount: 0, + } + keeper.SetStreamRecord(ctx, gvg3StreamRecord) + + outFlow3 := &types.OutFlow{ + ToAddress: gvg3.String(), + Rate: sdkmath.NewInt(150), + Status: types.OUT_FLOW_STATUS_FROZEN, + } + keeper.SetOutFlow(ctx, user, outFlow3) + + // resume the stream record + err := keeper.TryResumeStreamRecord(ctx, userStreamRecord, rate.MulRaw(int64(params.VersionedParams.ReserveTime))) + require.NoError(t, err) //only added static balance + found := keeper.ExistsAutoResumeRecord(ctx, ctx.BlockTime().Unix(), user) + require.True(t, found) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + + // add auto settle record + settleTime := ctx.BlockTime().Unix() + keeper.SetAutoSettleRecord(ctx, &types.AutoSettleRecord{ + Timestamp: settleTime, + Addr: user.String(), + }) + + keeper.AutoSettle(ctx) // this is for settle stream, it is counted + keeper.AutoSettle(ctx) + keeper.AutoSettle(ctx) + keeper.AutoSettle(ctx) + keeper.AutoSettle(ctx) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_FROZEN) + + keeper.AutoResume(ctx) + keeper.AutoResume(ctx) + keeper.AutoResume(ctx) + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + + timestamp := ctx.BlockTime().Unix() + ctx = ctx.WithBlockTime(time.Unix(timestamp+10, 0)) + keeper.AutoSettle(ctx) // it will pick up the auto settle record + autoSettles := keeper.GetAllAutoSettleRecord(ctx) + var record types.AutoSettleRecord + for _, settle := range autoSettles { + if settle.GetAddr() == user.String() { + record = settle + } + } + // old settle record removed, new settle record added + require.True(t, record.Timestamp != settleTime, "") + + userStreamRecord, _ = keeper.GetStreamRecord(ctx, user) + require.True(t, userStreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.True(t, userStreamRecord.NetflowRate.Equal(rate.Neg())) + require.True(t, userStreamRecord.FrozenNetflowRate.IsZero()) + + gvg1StreamRecord, _ = keeper.GetStreamRecord(ctx, gvg1) + require.True(t, gvg1StreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, gvg1StreamRecord.NetflowRate, sdk.NewInt(50)) + require.Equal(t, gvg1StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) + + gvg2StreamRecord, _ = keeper.GetStreamRecord(ctx, gvg2) + require.True(t, gvg2StreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, gvg2StreamRecord.NetflowRate, sdk.NewInt(100)) + require.Equal(t, gvg2StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) + + gvg3StreamRecord, _ = keeper.GetStreamRecord(ctx, gvg3) + require.True(t, gvg3StreamRecord.Status == types.STREAM_ACCOUNT_STATUS_ACTIVE) + require.Equal(t, gvg3StreamRecord.NetflowRate, sdk.NewInt(150)) + require.Equal(t, gvg3StreamRecord.FrozenNetflowRate, sdkmath.ZeroInt()) +} diff --git a/x/payment/module.go b/x/payment/module.go index 228832500..3dc2e77e5 100644 --- a/x/payment/module.go +++ b/x/payment/module.go @@ -145,6 +145,8 @@ func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} // EndBlock contains the logic that is automatically triggered at the end of each block func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + // set ForceUpdateStreamRecordKey to true in context to force update frozen stream record + ctx = ctx.WithValue(types.ForceUpdateStreamRecordKey, true) am.keeper.AutoResume(ctx) am.keeper.AutoSettle(ctx) return []abci.ValidatorUpdate{} diff --git a/x/payment/types/params.go b/x/payment/types/params.go index 13c9532f8..bc1fb52bd 100644 --- a/x/payment/types/params.go +++ b/x/payment/types/params.go @@ -109,6 +109,10 @@ func (p Params) Validate() error { return err } + if p.VersionedParams.ReserveTime <= p.ForcedSettleTime { + return fmt.Errorf("reserve time must be greater than force settle time") + } + return nil } diff --git a/x/payment/types/price.go b/x/payment/types/price.go index a1b144ae8..6b67c292b 100644 --- a/x/payment/types/price.go +++ b/x/payment/types/price.go @@ -10,6 +10,7 @@ type StreamRecordChange struct { RateChange sdkmath.Int StaticBalanceChange sdkmath.Int LockBalanceChange sdkmath.Int + FrozenRateChange sdkmath.Int } func NewDefaultStreamRecordChangeWithAddr(addr sdk.AccAddress) *StreamRecordChange { @@ -18,6 +19,7 @@ func NewDefaultStreamRecordChangeWithAddr(addr sdk.AccAddress) *StreamRecordChan RateChange: sdkmath.ZeroInt(), StaticBalanceChange: sdkmath.ZeroInt(), LockBalanceChange: sdkmath.ZeroInt(), + FrozenRateChange: sdkmath.ZeroInt(), } } @@ -36,6 +38,11 @@ func (change *StreamRecordChange) WithLockBalanceChange(lockBalanceChange sdkmat return change } +func (change *StreamRecordChange) WithFrozenRateChange(frozenRateChange sdkmath.Int) *StreamRecordChange { + change.FrozenRateChange = frozenRateChange + return change +} + type StoragePriceParams struct { PrimarySp uint32 PriceTime int64 diff --git a/x/payment/types/stream_record.go b/x/payment/types/stream_record.go index 22269dbf8..898d8b82a 100644 --- a/x/payment/types/stream_record.go +++ b/x/payment/types/stream_record.go @@ -13,5 +13,6 @@ func NewStreamRecord(account sdk.AccAddress, crudTimestamp int64) *StreamRecord BufferBalance: sdkmath.ZeroInt(), NetflowRate: sdkmath.ZeroInt(), LockBalance: sdkmath.ZeroInt(), + Status: STREAM_ACCOUNT_STATUS_ACTIVE, } } diff --git a/x/payment/types/types.go b/x/payment/types/types.go index 251b1ba17..64bbfa295 100644 --- a/x/payment/types/types.go +++ b/x/payment/types/types.go @@ -14,3 +14,8 @@ var ( const ( ForceUpdateStreamRecordKey = "force_update_stream_record" ) + +const ( + // GovernanceAddressLackBalanceLabel is the metrics label to notify that the governance account has no enough balance + GovernanceAddressLackBalanceLabel = "governance_address_lack_balance" +) diff --git a/x/payment/types/types_test.go b/x/payment/types/types_test.go new file mode 100644 index 000000000..70eae0762 --- /dev/null +++ b/x/payment/types/types_test.go @@ -0,0 +1,12 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestVerifyModuleAddress(t *testing.T) { + require.Equal(t, "0x25b2f7C1aA3cCCeF718e8e3A7Ec1A7C521eef2a9", GovernanceAddress.String()) + require.Equal(t, "0xdF5F0588f6B09f0B9E58D3426252db25Dc74E7a1", ValidatorTaxPoolAddress.String()) +} diff --git a/x/storage/abci.go b/x/storage/abci.go index e42e1b51b..b395f512c 100644 --- a/x/storage/abci.go +++ b/x/storage/abci.go @@ -31,6 +31,7 @@ func EndBlocker(ctx sdk.Context, keeper k.Keeper) { deleted, err := keeper.DeleteDiscontinueObjectsUntil(ctx, blockTime, deletionMax) if err != nil { ctx.Logger().Error("should not happen, fail to delete objects, err " + err.Error()) + panic("should not happen") } if deleted >= deletionMax { @@ -41,6 +42,7 @@ func EndBlocker(ctx sdk.Context, keeper k.Keeper) { _, err = keeper.DeleteDiscontinueBucketsUntil(ctx, blockTime, deletionMax-deleted) if err != nil { ctx.Logger().Error("should not happen, fail to delete buckets, err " + err.Error()) + panic("should not happen") } keeper.PersistDeleteInfo(ctx) diff --git a/x/storage/keeper/grpc_query.go b/x/storage/keeper/grpc_query.go index 6e9972a20..ddcc0e4dd 100644 --- a/x/storage/keeper/grpc_query.go +++ b/x/storage/keeper/grpc_query.go @@ -71,3 +71,50 @@ func (k Keeper) QueryLockFee(c context.Context, req *types.QueryLockFeeRequest) return &types.QueryLockFeeResponse{Amount: amount}, nil } + +func (k Keeper) HeadBucketExtra(c context.Context, req *types.QueryHeadBucketExtraRequest) (*types.QueryHeadBucketExtraResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(c) + bucketInfo, found := k.GetBucketInfo(ctx, req.BucketName) + if !found { + return nil, types.ErrNoSuchBucket + } + + internalBucketInfo := k.MustGetInternalBucketInfo(ctx, bucketInfo.Id) + + return &types.QueryHeadBucketExtraResponse{ + ExtraInfo: internalBucketInfo, + }, nil +} + +func (k Keeper) QueryIsPriceChanged(c context.Context, req *types.QueryIsPriceChangedRequest) (*types.QueryIsPriceChangedResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(c) + bucketInfo, found := k.GetBucketInfo(ctx, req.BucketName) + if !found { + return nil, types.ErrNoSuchBucket + } + + primarySp := k.MustGetPrimarySPForBucket(ctx, bucketInfo) + internalBucketInfo := k.MustGetInternalBucketInfo(ctx, bucketInfo.Id) + changed, currentPrice, currentTaxRate, newPrice, newTaxRate, err := k.IsPriceChanged(ctx, primarySp.Id, internalBucketInfo.PriceTime) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryIsPriceChangedResponse{ + Changed: changed, + CurrentReadPrice: currentPrice.ReadPrice, + CurrentPrimaryStorePrice: currentPrice.PrimaryStorePrice, + CurrentSecondaryStorePrice: currentPrice.ReadPrice, + CurrentValidatorTaxRate: currentTaxRate, + NewReadPrice: newPrice.ReadPrice, + NewPrimaryStorePrice: newPrice.PrimaryStorePrice, + NewSecondaryStorePrice: newPrice.SecondaryStorePrice, + NewValidatorTaxRate: newTaxRate, + }, nil +} diff --git a/x/storage/keeper/payment.go b/x/storage/keeper/payment.go index 0fe6734d9..7d324b9d9 100644 --- a/x/storage/keeper/payment.go +++ b/x/storage/keeper/payment.go @@ -160,25 +160,37 @@ func (k Keeper) UnlockAndChargeObjectStoreFee(ctx sdk.Context, primarySpId uint3 return k.ChargeObjectStoreFee(ctx, primarySpId, bucketInfo, internalBucketInfo, objectInfo) } -func (k Keeper) IsPriceChanged(ctx sdk.Context, primarySpId uint32, priceTime int64) (bool, error) { +func (k Keeper) IsPriceChanged(ctx sdk.Context, primarySpId uint32, priceTime int64) (bool, *types.StoragePrice, sdk.Dec, *types.StoragePrice, sdk.Dec, error) { prePrice, err := k.paymentKeeper.GetStoragePrice(ctx, types.StoragePriceParams{ PrimarySp: primarySpId, PriceTime: priceTime, }) if err != nil { - return false, fmt.Errorf("get storage price failed: %w", err) + return false, nil, sdk.ZeroDec(), nil, sdk.ZeroDec(), err } currentPrice, err := k.paymentKeeper.GetStoragePrice(ctx, types.StoragePriceParams{ PrimarySp: primarySpId, PriceTime: ctx.BlockTime().Unix(), }) if err != nil { - return false, fmt.Errorf("get storage price failed: %w", err) + return false, nil, sdk.ZeroDec(), nil, sdk.ZeroDec(), err + } + + preParams, err := k.paymentKeeper.GetVersionedParamsWithTs(ctx, priceTime) + if err != nil { + return false, nil, sdk.ZeroDec(), nil, sdk.ZeroDec(), err + } + + currentParams, err := k.paymentKeeper.GetVersionedParamsWithTs(ctx, ctx.BlockTime().Unix()) + if err != nil { + return false, nil, sdk.ZeroDec(), nil, sdk.ZeroDec(), err } return !(prePrice.ReadPrice.Equal(currentPrice.ReadPrice) && - prePrice.PrimaryStorePrice.Equal(currentPrice.PrimaryStorePrice) && - prePrice.SecondaryStorePrice.Equal(currentPrice.SecondaryStorePrice)), nil + prePrice.PrimaryStorePrice.Equal(currentPrice.PrimaryStorePrice) && + prePrice.SecondaryStorePrice.Equal(currentPrice.SecondaryStorePrice) && + preParams.ValidatorTaxRate.Equal(currentParams.ValidatorTaxRate)), + &prePrice, preParams.ValidatorTaxRate, ¤tPrice, currentParams.ValidatorTaxRate, nil } func (k Keeper) ChargeObjectStoreFee(ctx sdk.Context, primarySpId uint32, bucketInfo *storagetypes.BucketInfo, @@ -188,7 +200,7 @@ func (k Keeper) ChargeObjectStoreFee(ctx sdk.Context, primarySpId uint32, bucket return fmt.Errorf("get charge size error: %w", err) } - priceChanged, err := k.IsPriceChanged(ctx, primarySpId, internalBucketInfo.PriceTime) + priceChanged, _, _, _, _, err := k.IsPriceChanged(ctx, primarySpId, internalBucketInfo.PriceTime) if err != nil { return fmt.Errorf("check whether price changed error: %w", err) } @@ -220,46 +232,19 @@ func (k Keeper) UnChargeObjectStoreFee(ctx sdk.Context, primarySpId uint32, buck return fmt.Errorf("get charge size error: %w", err) } - priceChanged, err := k.IsPriceChanged(ctx, primarySpId, internalBucketInfo.PriceTime) + err = k.ChargeViaObjectChange(ctx, bucketInfo, internalBucketInfo, objectInfo, chargeSize, true) if err != nil { - return fmt.Errorf("check whether price changed error: %w", err) - } - - oldInternalBucketInfo := &storagetypes.InternalBucketInfo{ - PriceTime: internalBucketInfo.PriceTime, - TotalChargeSize: internalBucketInfo.TotalChargeSize, - LocalVirtualGroups: internalBucketInfo.LocalVirtualGroups, - } - - if !priceChanged { - err = k.ChargeViaObjectChange(ctx, bucketInfo, internalBucketInfo, objectInfo, chargeSize, true) - if err != nil { - return fmt.Errorf("apply object store bill error: %w", err) - } - } else { - err = k.ChargeViaBucketChange(ctx, bucketInfo, internalBucketInfo, func(bi *storagetypes.BucketInfo, ibi *storagetypes.InternalBucketInfo) error { - ibi.TotalChargeSize -= chargeSize - for _, lvg := range ibi.LocalVirtualGroups { - if lvg.Id == objectInfo.LocalVirtualGroupId { - lvg.TotalChargeSize -= chargeSize - break - } - } - return nil - }) - if err != nil { - return fmt.Errorf("apply object store bill error: %w", err) - } + return fmt.Errorf("apply object store bill error: %w", err) } blockTime := ctx.BlockTime().Unix() - versionParams, err := k.paymentKeeper.GetVersionedParamsWithTs(ctx, oldInternalBucketInfo.PriceTime) + versionParams, err := k.paymentKeeper.GetVersionedParamsWithTs(ctx, internalBucketInfo.PriceTime) if err != nil { return fmt.Errorf("failed to get versioned params: %w", err) } timeToPay := objectInfo.CreateAt + int64(versionParams.ReserveTime) - blockTime if timeToPay > 0 { // store less than reserve time - err = k.ChargeObjectStoreFeeForEarlyDeletion(ctx, bucketInfo, oldInternalBucketInfo, objectInfo, chargeSize, timeToPay) + err = k.ChargeObjectStoreFeeForEarlyDeletion(ctx, bucketInfo, internalBucketInfo, objectInfo, chargeSize, timeToPay) forced, _ := ctx.Value(types.ForceUpdateStreamRecordKey).(bool) // force update in end block if !forced && err != nil { return fmt.Errorf("fail to pay for early deletion, error: %w", err) @@ -291,8 +276,8 @@ func (k Keeper) ChargeObjectStoreFeeForEarlyDeletion(ctx sdk.Context, bucketInfo primaryRate := price.PrimaryStorePrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() if primaryRate.IsPositive() { primaryTotalFlowRate = primaryRate - err = k.paymentKeeper.Withdraw(ctx, paymentAddr, sdk.MustAccAddressFromHex(gvgFamily.VirtualPaymentAddress), - primaryTotalFlowRate.MulRaw(timeToPay)) + _, err = k.paymentKeeper.UpdateStreamRecordByAddr(ctx, types.NewDefaultStreamRecordChangeWithAddr(sdk.MustAccAddressFromHex(gvgFamily.VirtualPaymentAddress)). + WithStaticBalanceChange(primaryTotalFlowRate.MulRaw(timeToPay))) if err != nil { return fmt.Errorf("fail to pay GVG family: %s", err) } @@ -318,8 +303,8 @@ func (k Keeper) ChargeObjectStoreFeeForEarlyDeletion(ctx sdk.Context, bucketInfo secondaryRate = secondaryRate.MulRaw(int64(len(gvg.SecondarySpIds))) if secondaryRate.IsPositive() { secondaryTotalFlowRate = secondaryTotalFlowRate.Add(secondaryRate) - err = k.paymentKeeper.Withdraw(ctx, paymentAddr, sdk.MustAccAddressFromHex(gvg.VirtualPaymentAddress), - secondaryTotalFlowRate.MulRaw(timeToPay)) + _, err = k.paymentKeeper.UpdateStreamRecordByAddr(ctx, types.NewDefaultStreamRecordChangeWithAddr(sdk.MustAccAddressFromHex(gvg.VirtualPaymentAddress)). + WithStaticBalanceChange(secondaryTotalFlowRate.MulRaw(timeToPay))) if err != nil { return fmt.Errorf("fail to pay GVG: %s", err) } @@ -332,13 +317,20 @@ func (k Keeper) ChargeObjectStoreFeeForEarlyDeletion(ctx sdk.Context, bucketInfo } validatorTaxRate := versionedParams.ValidatorTaxRate.MulInt(primaryTotalFlowRate.Add(secondaryTotalFlowRate)).TruncateInt() if validatorTaxRate.IsPositive() { - err = k.paymentKeeper.Withdraw(ctx, paymentAddr, types.ValidatorTaxPoolAddress, - validatorTaxRate.MulRaw(timeToPay)) + _, err = k.paymentKeeper.UpdateStreamRecordByAddr(ctx, types.NewDefaultStreamRecordChangeWithAddr(types.ValidatorTaxPoolAddress). + WithStaticBalanceChange(validatorTaxRate.MulRaw(timeToPay))) if err != nil { return fmt.Errorf("fail to pay validator: %s", err) } } + total := primaryTotalFlowRate.Add(secondaryTotalFlowRate).Add(validatorTaxRate).MulRaw(timeToPay) + _, err = k.paymentKeeper.UpdateStreamRecordByAddr(ctx, types.NewDefaultStreamRecordChangeWithAddr(paymentAddr). + WithStaticBalanceChange(total.Neg())) + if err != nil { + return fmt.Errorf("fail to substrct from payment account: %s", err) + } + return nil } @@ -355,7 +347,6 @@ func (k Keeper) ChargeViaBucketChange(ctx sdk.Context, bucketInfo *storagetypes. if err = changeFunc(bucketInfo, internalBucketInfo); err != nil { return errors.Wrapf(err, "change bucket internal info failed") } - // calculate new bill internalBucketInfo.PriceTime = ctx.BlockTime().Unix() newBill, err := k.GetBucketReadStoreBill(ctx, bucketInfo, internalBucketInfo) @@ -391,12 +382,6 @@ func (k Keeper) ChargeViaObjectChange(ctx sdk.Context, bucketInfo *storagetypes. return fmt.Errorf("get storage price failed: %w", err) } - // primary sp total rate - primaryTotalFlowRate := sdk.ZeroInt() - - // secondary sp total rate - secondaryTotalFlowRate := sdk.ZeroInt() - var lvg *storagetypes.LocalVirtualGroup for _, l := range internalBucketInfo.LocalVirtualGroups { if l.Id == objectInfo.LocalVirtualGroupId { @@ -408,7 +393,10 @@ func (k Keeper) ChargeViaObjectChange(ctx sdk.Context, bucketInfo *storagetypes. // primary sp primaryRate := price.PrimaryStorePrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() if primaryRate.IsPositive() { - primaryTotalFlowRate = primaryTotalFlowRate.Add(primaryRate) + userFlows.Flows = append(userFlows.Flows, types.OutFlow{ + ToAddress: gvgFamily.VirtualPaymentAddress, + Rate: primaryRate, + }) } //secondary sp @@ -424,21 +412,13 @@ func (k Keeper) ChargeViaObjectChange(ctx sdk.Context, bucketInfo *storagetypes. ToAddress: gvg.VirtualPaymentAddress, Rate: secondaryRate, }) - secondaryTotalFlowRate = secondaryTotalFlowRate.Add(secondaryRate) - } - - if primaryTotalFlowRate.IsPositive() { - userFlows.Flows = append(userFlows.Flows, types.OutFlow{ - ToAddress: gvgFamily.VirtualPaymentAddress, - Rate: primaryTotalFlowRate, - }) } versionedParams, err := k.paymentKeeper.GetVersionedParamsWithTs(ctx, internalBucketInfo.PriceTime) if err != nil { return fmt.Errorf("failed to get validator tax rate: %w, time: %d", err, internalBucketInfo.PriceTime) } - validatorTaxRate := versionedParams.ValidatorTaxRate.MulInt(primaryTotalFlowRate.Add(secondaryTotalFlowRate)).TruncateInt() + validatorTaxRate := versionedParams.ValidatorTaxRate.MulInt(primaryRate.Add(secondaryRate)).TruncateInt() if validatorTaxRate.IsPositive() { userFlows.Flows = append(userFlows.Flows, types.OutFlow{ ToAddress: types.ValidatorTaxPoolAddress.String(), @@ -593,12 +573,20 @@ func (k Keeper) GetObjectLockFee(ctx sdk.Context, primarySpId uint32, priceTime if err != nil { return amount, fmt.Errorf("get charge size error: %w", err) } + + primaryRate := price.PrimaryStorePrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() + secondarySPNum := int64(k.GetExpectSecondarySPNumForECObject(ctx, priceTime)) - rate := price.PrimaryStorePrice.Add(price.SecondaryStorePrice.MulInt64(secondarySPNum)).MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() + secondaryRate := price.SecondaryStorePrice.MulInt(sdkmath.NewIntFromUint64(chargeSize)).TruncateInt() + secondaryRate = secondaryRate.MulRaw(int64(secondarySPNum)) + versionedParams, err := k.paymentKeeper.GetVersionedParamsWithTs(ctx, priceTime) if err != nil { return amount, fmt.Errorf("get versioned reserve time error: %w", err) } + validatorTaxRate := versionedParams.ValidatorTaxRate.MulInt(primaryRate.Add(secondaryRate)).TruncateInt() + + rate := primaryRate.Add(secondaryRate).Add(validatorTaxRate) // should also lock for validator tax pool amount = rate.Mul(sdkmath.NewIntFromUint64(versionedParams.ReserveTime)) return amount, nil } diff --git a/x/storage/keeper/payment_test.go b/x/storage/keeper/payment_test.go index f8962eb15..1d3d087e1 100644 --- a/x/storage/keeper/payment_test.go +++ b/x/storage/keeper/payment_test.go @@ -122,12 +122,13 @@ func (s *TestSuite) TestGetObjectLockFee() { amount, err := s.storageKeeper.GetObjectLockFee(s.ctx, 100, time.Now().Unix(), uint64(payloadSize)) s.Require().NoError(err) secondarySPNum := int64(s.storageKeeper.GetExpectSecondarySPNumForECObject(s.ctx, time.Now().Unix())) - expectedAmount := price.PrimaryStorePrice.Add(price.SecondaryStorePrice.MulInt64(secondarySPNum)). - MulInt64(payloadSize).MulInt64(int64(params.VersionedParams.ReserveTime)).TruncateInt() + spRate := price.PrimaryStorePrice.Add(price.SecondaryStorePrice.MulInt64(secondarySPNum)).MulInt64(payloadSize) + validatorTaxRate := params.VersionedParams.ValidatorTaxRate.MulInt(spRate.TruncateInt()) + expectedAmount := spRate.Add(validatorTaxRate).MulInt64(int64(params.VersionedParams.ReserveTime)).TruncateInt() s.Require().True(amount.Equal(expectedAmount)) } -func (s *TestSuite) TestGetBucketBill() { +func (s *TestSuite) TestGetBucketReadBill() { gvgFamily := &virtualgroupmoduletypes.GlobalVirtualGroupFamily{ Id: 1, VirtualPaymentAddress: sample.RandAccAddress().String(), @@ -186,9 +187,37 @@ func (s *TestSuite) TestGetBucketBill() { taxPoolRate := params.VersionedParams.ValidatorTaxRate.MulInt(readRate).TruncateInt() s.Require().Equal(flows.Flows[1].ToAddress, paymenttypes.ValidatorTaxPoolAddress.String()) s.Require().Equal(flows.Flows[1].Rate, taxPoolRate) +} + +func (s *TestSuite) TestGetBucketReadStoreBill() { + gvgFamily := &virtualgroupmoduletypes.GlobalVirtualGroupFamily{ + Id: 1, + VirtualPaymentAddress: sample.RandAccAddress().String(), + } + s.virtualGroupKeeper.EXPECT().GetGVGFamily(gomock.Any(), gomock.Any()). + Return(gvgFamily, true).AnyTimes() + + primarySp := &sptypes.StorageProvider{ + Status: sptypes.STATUS_IN_SERVICE, + Id: 100, + OperatorAddress: sample.RandAccAddress().String(), + FundingAddress: sample.RandAccAddress().String()} + s.spKeeper.EXPECT().GetStorageProvider(gomock.Any(), gomock.Eq(primarySp.Id)). + Return(primarySp, true).AnyTimes() + + price := paymenttypes.StoragePrice{ + ReadPrice: sdk.NewDec(100), + PrimaryStorePrice: sdk.NewDec(1000), + SecondaryStorePrice: sdk.NewDec(500), + } + s.paymentKeeper.EXPECT().GetStoragePrice(gomock.Any(), gomock.Any()). + Return(price, nil).AnyTimes() + params := paymenttypes.DefaultParams() + s.paymentKeeper.EXPECT().GetVersionedParamsWithTs(gomock.Any(), gomock.Any()). + Return(params.VersionedParams, nil).AnyTimes() // none empty bucket - bucketInfo = &types.BucketInfo{ + bucketInfo := &types.BucketInfo{ Owner: "", BucketName: "bucketname", Id: sdk.NewUint(1), @@ -197,7 +226,7 @@ func (s *TestSuite) TestGetBucketBill() { ChargedReadQuota: 100, } - internalBucketInfo = &types.InternalBucketInfo{ + internalBucketInfo := &types.InternalBucketInfo{ TotalChargeSize: 300, LocalVirtualGroups: []*types.LocalVirtualGroup{ { @@ -229,7 +258,7 @@ func (s *TestSuite) TestGetBucketBill() { s.virtualGroupKeeper.EXPECT().GetGVG(gomock.Any(), gvg2.Id). Return(gvg2, true).AnyTimes() - flows, err = s.storageKeeper.GetBucketReadStoreBill(s.ctx, bucketInfo, internalBucketInfo) + flows, err := s.storageKeeper.GetBucketReadStoreBill(s.ctx, bucketInfo, internalBucketInfo) s.Require().NoError(err) gvg1StoreSize := internalBucketInfo.LocalVirtualGroups[0].TotalChargeSize * uint64(len(gvg1.SecondarySpIds)) @@ -242,13 +271,13 @@ func (s *TestSuite) TestGetBucketBill() { s.Require().Equal(flows.Flows[1].ToAddress, gvg2.VirtualPaymentAddress) s.Require().Equal(flows.Flows[1].Rate, gvg2StoreRate) - readRate = price.ReadPrice.MulInt64(int64(bucketInfo.ChargedReadQuota)).TruncateInt() + readRate := price.ReadPrice.MulInt64(int64(bucketInfo.ChargedReadQuota)).TruncateInt() primaryStoreRate := price.PrimaryStorePrice.MulInt64(int64(internalBucketInfo.TotalChargeSize)).TruncateInt() s.Require().Equal(flows.Flows[2].ToAddress, gvgFamily.VirtualPaymentAddress) s.Require().Equal(flows.Flows[2].Rate, readRate.Add(primaryStoreRate)) totalRate := readRate.Add(primaryStoreRate).Add(gvg1StoreRate).Add(gvg2StoreRate) - taxPoolRate = params.VersionedParams.ValidatorTaxRate.MulInt(totalRate).TruncateInt() + taxPoolRate := params.VersionedParams.ValidatorTaxRate.MulInt(totalRate).TruncateInt() s.Require().Equal(flows.Flows[3].ToAddress, paymenttypes.ValidatorTaxPoolAddress.String()) s.Require().Equal(flows.Flows[3].Rate, taxPoolRate) } diff --git a/x/storage/types/query.pb.go b/x/storage/types/query.pb.go index ee814f80c..8a84a7bcb 100644 --- a/x/storage/types/query.pb.go +++ b/x/storage/types/query.pb.go @@ -1713,6 +1713,190 @@ func (m *QueryLockFeeResponse) XXX_DiscardUnknown() { var xxx_messageInfo_QueryLockFeeResponse proto.InternalMessageInfo +type QueryHeadBucketExtraRequest struct { + BucketName string `protobuf:"bytes,1,opt,name=bucket_name,json=bucketName,proto3" json:"bucket_name,omitempty"` +} + +func (m *QueryHeadBucketExtraRequest) Reset() { *m = QueryHeadBucketExtraRequest{} } +func (m *QueryHeadBucketExtraRequest) String() string { return proto.CompactTextString(m) } +func (*QueryHeadBucketExtraRequest) ProtoMessage() {} +func (*QueryHeadBucketExtraRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_b1b80b580af04cb0, []int{35} +} +func (m *QueryHeadBucketExtraRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryHeadBucketExtraRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryHeadBucketExtraRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryHeadBucketExtraRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryHeadBucketExtraRequest.Merge(m, src) +} +func (m *QueryHeadBucketExtraRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryHeadBucketExtraRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryHeadBucketExtraRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryHeadBucketExtraRequest proto.InternalMessageInfo + +func (m *QueryHeadBucketExtraRequest) GetBucketName() string { + if m != nil { + return m.BucketName + } + return "" +} + +type QueryHeadBucketExtraResponse struct { + ExtraInfo *InternalBucketInfo `protobuf:"bytes,1,opt,name=extra_info,json=extraInfo,proto3" json:"extra_info,omitempty"` +} + +func (m *QueryHeadBucketExtraResponse) Reset() { *m = QueryHeadBucketExtraResponse{} } +func (m *QueryHeadBucketExtraResponse) String() string { return proto.CompactTextString(m) } +func (*QueryHeadBucketExtraResponse) ProtoMessage() {} +func (*QueryHeadBucketExtraResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_b1b80b580af04cb0, []int{36} +} +func (m *QueryHeadBucketExtraResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryHeadBucketExtraResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryHeadBucketExtraResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryHeadBucketExtraResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryHeadBucketExtraResponse.Merge(m, src) +} +func (m *QueryHeadBucketExtraResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryHeadBucketExtraResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryHeadBucketExtraResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryHeadBucketExtraResponse proto.InternalMessageInfo + +func (m *QueryHeadBucketExtraResponse) GetExtraInfo() *InternalBucketInfo { + if m != nil { + return m.ExtraInfo + } + return nil +} + +type QueryIsPriceChangedRequest struct { + BucketName string `protobuf:"bytes,1,opt,name=bucket_name,json=bucketName,proto3" json:"bucket_name,omitempty"` +} + +func (m *QueryIsPriceChangedRequest) Reset() { *m = QueryIsPriceChangedRequest{} } +func (m *QueryIsPriceChangedRequest) String() string { return proto.CompactTextString(m) } +func (*QueryIsPriceChangedRequest) ProtoMessage() {} +func (*QueryIsPriceChangedRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_b1b80b580af04cb0, []int{37} +} +func (m *QueryIsPriceChangedRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryIsPriceChangedRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryIsPriceChangedRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryIsPriceChangedRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryIsPriceChangedRequest.Merge(m, src) +} +func (m *QueryIsPriceChangedRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryIsPriceChangedRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryIsPriceChangedRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryIsPriceChangedRequest proto.InternalMessageInfo + +func (m *QueryIsPriceChangedRequest) GetBucketName() string { + if m != nil { + return m.BucketName + } + return "" +} + +type QueryIsPriceChangedResponse struct { + Changed bool `protobuf:"varint,1,opt,name=changed,proto3" json:"changed,omitempty"` + CurrentReadPrice github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=current_read_price,json=currentReadPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"current_read_price"` + CurrentPrimaryStorePrice github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=current_primary_store_price,json=currentPrimaryStorePrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"current_primary_store_price"` + CurrentSecondaryStorePrice github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=current_secondary_store_price,json=currentSecondaryStorePrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"current_secondary_store_price"` + CurrentValidatorTaxRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=current_validator_tax_rate,json=currentValidatorTaxRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"current_validator_tax_rate"` + NewReadPrice github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=new_read_price,json=newReadPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"new_read_price"` + NewPrimaryStorePrice github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,7,opt,name=new_primary_store_price,json=newPrimaryStorePrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"new_primary_store_price"` + NewSecondaryStorePrice github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,8,opt,name=new_secondary_store_price,json=newSecondaryStorePrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"new_secondary_store_price"` + NewValidatorTaxRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,9,opt,name=new_validator_tax_rate,json=newValidatorTaxRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"new_validator_tax_rate"` +} + +func (m *QueryIsPriceChangedResponse) Reset() { *m = QueryIsPriceChangedResponse{} } +func (m *QueryIsPriceChangedResponse) String() string { return proto.CompactTextString(m) } +func (*QueryIsPriceChangedResponse) ProtoMessage() {} +func (*QueryIsPriceChangedResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_b1b80b580af04cb0, []int{38} +} +func (m *QueryIsPriceChangedResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryIsPriceChangedResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryIsPriceChangedResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryIsPriceChangedResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryIsPriceChangedResponse.Merge(m, src) +} +func (m *QueryIsPriceChangedResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryIsPriceChangedResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryIsPriceChangedResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryIsPriceChangedResponse proto.InternalMessageInfo + +func (m *QueryIsPriceChangedResponse) GetChanged() bool { + if m != nil { + return m.Changed + } + return false +} + func init() { proto.RegisterType((*QueryParamsRequest)(nil), "greenfield.storage.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "greenfield.storage.QueryParamsResponse") @@ -1749,135 +1933,160 @@ func init() { proto.RegisterType((*QueryPolicyByIdResponse)(nil), "greenfield.storage.QueryPolicyByIdResponse") proto.RegisterType((*QueryLockFeeRequest)(nil), "greenfield.storage.QueryLockFeeRequest") proto.RegisterType((*QueryLockFeeResponse)(nil), "greenfield.storage.QueryLockFeeResponse") + proto.RegisterType((*QueryHeadBucketExtraRequest)(nil), "greenfield.storage.QueryHeadBucketExtraRequest") + proto.RegisterType((*QueryHeadBucketExtraResponse)(nil), "greenfield.storage.QueryHeadBucketExtraResponse") + proto.RegisterType((*QueryIsPriceChangedRequest)(nil), "greenfield.storage.QueryIsPriceChangedRequest") + proto.RegisterType((*QueryIsPriceChangedResponse)(nil), "greenfield.storage.QueryIsPriceChangedResponse") } func init() { proto.RegisterFile("greenfield/storage/query.proto", fileDescriptor_b1b80b580af04cb0) } var fileDescriptor_b1b80b580af04cb0 = []byte{ - // 1956 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x59, 0xcd, 0x8f, 0x1c, 0x47, - 0x15, 0x77, 0xef, 0x9a, 0x65, 0xb7, 0x66, 0xb1, 0x43, 0xb1, 0x01, 0xa7, 0xb3, 0x1e, 0xdb, 0x1d, - 0xb0, 0x37, 0x8e, 0x77, 0xda, 0xeb, 0xc4, 0x88, 0x55, 0x3e, 0xd0, 0x0e, 0xf1, 0x2e, 0x23, 0xf9, - 0x8b, 0xf1, 0xca, 0x88, 0x95, 0x50, 0xab, 0xa6, 0xbb, 0x66, 0xdc, 0xec, 0x74, 0x57, 0xbb, 0xbb, - 0xc7, 0x30, 0x19, 0xcd, 0x81, 0x5c, 0xe0, 0x88, 0x40, 0x48, 0x11, 0x22, 0x12, 0x08, 0x81, 0x80, - 0x0b, 0x97, 0x5c, 0x38, 0x71, 0xe1, 0x10, 0x09, 0x21, 0x45, 0xe1, 0x82, 0x72, 0x88, 0x90, 0xcd, - 0x1f, 0x12, 0x75, 0xd5, 0xeb, 0xee, 0xea, 0xaf, 0xe9, 0xd9, 0xec, 0x9e, 0x76, 0xba, 0xfa, 0xbd, - 0x57, 0xbf, 0xf7, 0x51, 0xaf, 0xde, 0xaf, 0x17, 0x35, 0x07, 0x3e, 0xa5, 0x6e, 0xdf, 0xa6, 0x43, - 0x4b, 0x0f, 0x42, 0xe6, 0x93, 0x01, 0xd5, 0x1f, 0x8f, 0xa8, 0x3f, 0x6e, 0x79, 0x3e, 0x0b, 0x19, - 0xc6, 0xe9, 0xfb, 0x16, 0xbc, 0x57, 0xaf, 0x9a, 0x2c, 0x70, 0x58, 0xa0, 0xf7, 0x48, 0x00, 0xc2, - 0xfa, 0x93, 0xad, 0x1e, 0x0d, 0xc9, 0x96, 0xee, 0x91, 0x81, 0xed, 0x92, 0xd0, 0x66, 0xae, 0xd0, - 0x57, 0x5f, 0x10, 0xb2, 0x06, 0x7f, 0xd2, 0xc5, 0x03, 0xbc, 0x5a, 0x1b, 0xb0, 0x01, 0x13, 0xeb, - 0xd1, 0x2f, 0x58, 0x5d, 0x1f, 0x30, 0x36, 0x18, 0x52, 0x9d, 0x78, 0xb6, 0x4e, 0x5c, 0x97, 0x85, - 0xdc, 0x5a, 0xac, 0xa3, 0x49, 0x70, 0x3d, 0xea, 0x3b, 0x76, 0x10, 0xd8, 0xcc, 0xd5, 0x4d, 0xe6, - 0x38, 0xc9, 0x96, 0x97, 0xca, 0x65, 0xc2, 0xb1, 0x47, 0x63, 0x33, 0x17, 0x4a, 0xbc, 0xf6, 0x88, - 0x4f, 0x9c, 0x58, 0xa0, 0x2c, 0x2c, 0xb2, 0x81, 0x97, 0xa4, 0xf7, 0x4f, 0x6c, 0x3f, 0x1c, 0x91, - 0xe1, 0xc0, 0x67, 0x23, 0x4f, 0x16, 0xd2, 0xd6, 0x10, 0xfe, 0x5e, 0x14, 0x9d, 0xfb, 0xdc, 0x72, - 0x97, 0x3e, 0x1e, 0xd1, 0x20, 0xd4, 0xee, 0xa1, 0xaf, 0x64, 0x56, 0x03, 0x8f, 0xb9, 0x01, 0xc5, - 0xdf, 0x42, 0x4b, 0x02, 0xc1, 0x39, 0xe5, 0xa2, 0xb2, 0xd1, 0xb8, 0xa1, 0xb6, 0x8a, 0x91, 0x6f, - 0x09, 0x9d, 0xf6, 0xe9, 0x0f, 0x3f, 0xbd, 0x70, 0xaa, 0x0b, 0xf2, 0xda, 0x9b, 0xe8, 0xbc, 0x64, - 0xb0, 0x3d, 0xde, 0xb7, 0x1d, 0x1a, 0x84, 0xc4, 0xf1, 0x60, 0x47, 0xbc, 0x8e, 0x56, 0xc2, 0x78, - 0x8d, 0x5b, 0x5f, 0xec, 0xa6, 0x0b, 0xda, 0x01, 0x6a, 0x56, 0xa9, 0x1f, 0x1b, 0xda, 0x36, 0xfa, - 0x2a, 0xb7, 0xfd, 0x5d, 0x4a, 0xac, 0xf6, 0xc8, 0x3c, 0xa4, 0x61, 0x8c, 0xe9, 0x02, 0x6a, 0xf4, - 0xf8, 0x82, 0xe1, 0x12, 0x87, 0x72, 0xc3, 0x2b, 0x5d, 0x24, 0x96, 0xee, 0x12, 0x87, 0x6a, 0xdb, - 0x48, 0xcd, 0xa9, 0xb6, 0xc7, 0x1d, 0x2b, 0x56, 0x7f, 0x11, 0xad, 0x80, 0xba, 0x6d, 0x81, 0xf2, - 0xb2, 0x58, 0xe8, 0x58, 0xda, 0x01, 0xfa, 0x5a, 0x61, 0x57, 0x70, 0xe5, 0xdb, 0xc9, 0xb6, 0xb6, - 0xdb, 0x67, 0xe0, 0x4f, 0xb3, 0xcc, 0x1f, 0xa1, 0xd8, 0x71, 0xfb, 0x2c, 0x86, 0x15, 0xfd, 0xd6, - 0x0e, 0x24, 0x8f, 0xee, 0xf5, 0x7e, 0x44, 0xcd, 0xb9, 0x3d, 0x8a, 0x04, 0x18, 0xd7, 0x10, 0x02, - 0x0b, 0x42, 0x40, 0x2c, 0x15, 0x5c, 0x16, 0xb6, 0x73, 0x2e, 0x83, 0x7a, 0xea, 0xb2, 0x58, 0xe8, - 0x58, 0xda, 0xdf, 0x15, 0xc9, 0xe7, 0x18, 0x57, 0xea, 0x73, 0xac, 0x58, 0xe3, 0xb3, 0x50, 0x14, - 0x3e, 0xb3, 0xe4, 0x37, 0xfe, 0x21, 0x5a, 0x1b, 0x0c, 0x59, 0x8f, 0x0c, 0x0d, 0x28, 0x75, 0x83, - 0xd7, 0x3a, 0xf7, 0xa0, 0x71, 0xe3, 0x15, 0xd9, 0x92, 0x7c, 0x16, 0x5a, 0x7b, 0x5c, 0xe9, 0xa1, - 0x58, 0xda, 0x8b, 0x96, 0xba, 0x78, 0x50, 0x58, 0xd3, 0x08, 0x40, 0xbf, 0x6d, 0x07, 0xa1, 0x88, - 0x7a, 0x7c, 0x56, 0xf0, 0x2e, 0x42, 0x69, 0x47, 0x01, 0xe4, 0x97, 0x5b, 0xd0, 0x45, 0xa2, 0xf6, - 0xd3, 0x12, 0xbd, 0x0a, 0xda, 0x4f, 0xeb, 0x3e, 0x19, 0x50, 0xd0, 0xed, 0x4a, 0x9a, 0xda, 0x9f, - 0x14, 0x74, 0xae, 0xb8, 0x07, 0xc4, 0x67, 0x07, 0xad, 0x4a, 0x35, 0x11, 0x15, 0xf9, 0xe2, 0x1c, - 0x45, 0xd1, 0x48, 0x8b, 0x22, 0xc0, 0x7b, 0x19, 0x9c, 0x22, 0x2e, 0x57, 0x6a, 0x71, 0x8a, 0xfd, - 0x33, 0x40, 0xdf, 0x55, 0xa4, 0x60, 0x88, 0x74, 0x9c, 0x74, 0x30, 0xf2, 0x85, 0xba, 0x50, 0x38, - 0x7a, 0x3f, 0x57, 0xd0, 0xa5, 0x3c, 0x88, 0xf6, 0x18, 0x7c, 0xb7, 0x4e, 0x1a, 0x4e, 0xe6, 0x28, - 0x2f, 0xe4, 0x8e, 0x72, 0x26, 0x71, 0x49, 0x3c, 0xd2, 0xc4, 0x49, 0x85, 0x3d, 0x33, 0x71, 0x52, - 0x65, 0x37, 0xd2, 0xca, 0x3e, 0xc1, 0xc4, 0x5d, 0x43, 0x67, 0x39, 0xce, 0xbb, 0xbb, 0xfb, 0x71, - 0x80, 0x5e, 0x40, 0xcb, 0x21, 0x3b, 0xa4, 0x6e, 0x7a, 0x5e, 0xbf, 0xc8, 0x9f, 0x3b, 0x96, 0xf6, - 0x03, 0xe8, 0x22, 0x22, 0xa6, 0x5c, 0x27, 0x39, 0xac, 0x2b, 0x0e, 0x0d, 0x89, 0x61, 0x91, 0x90, - 0x40, 0x50, 0xb5, 0xea, 0x4a, 0xbc, 0x43, 0x43, 0xf2, 0x36, 0x09, 0x49, 0x77, 0xd9, 0x81, 0x5f, - 0x89, 0x69, 0xe1, 0xf1, 0xe7, 0x31, 0x2d, 0x34, 0x4b, 0x4c, 0x7f, 0x1f, 0x3d, 0xcf, 0x4d, 0xf3, - 0x63, 0x2b, 0x5b, 0x7e, 0xab, 0x68, 0xf9, 0x52, 0x99, 0x65, 0xae, 0x58, 0x62, 0xf8, 0xa7, 0x0a, - 0x5a, 0x17, 0x77, 0x10, 0x1b, 0xda, 0xe6, 0x78, 0x97, 0xf9, 0x3b, 0xa6, 0xc9, 0x46, 0x6e, 0xd2, - 0x5b, 0x55, 0xb4, 0xec, 0xd3, 0x80, 0x8d, 0x7c, 0x33, 0x6e, 0xac, 0xc9, 0x33, 0xbe, 0x85, 0xbe, - 0xec, 0xf9, 0xb6, 0x6b, 0xda, 0x1e, 0x19, 0x1a, 0xc4, 0xb2, 0x7c, 0x1a, 0x04, 0xa2, 0x8e, 0xda, - 0xe7, 0x3e, 0xfe, 0x60, 0x73, 0x0d, 0x92, 0xb9, 0x23, 0xde, 0x3c, 0x08, 0x7d, 0xdb, 0x1d, 0x74, - 0x9f, 0x4b, 0x54, 0x60, 0x5d, 0x7b, 0x18, 0xdf, 0xa2, 0x05, 0x08, 0xe0, 0xe4, 0x4d, 0xb4, 0xe4, - 0xf1, 0x77, 0xe0, 0xe1, 0x79, 0xd9, 0xc3, 0x74, 0xce, 0x68, 0x09, 0x03, 0x5d, 0x10, 0xd6, 0x3e, - 0x89, 0x7d, 0x7b, 0x48, 0x7d, 0xbb, 0x3f, 0xbe, 0x9f, 0x08, 0xc6, 0xbe, 0xbd, 0x86, 0x96, 0x99, - 0x47, 0x7d, 0x12, 0x32, 0x5f, 0xf8, 0x36, 0x03, 0x76, 0x22, 0x59, 0x7b, 0x88, 0xf3, 0xb7, 0xcd, - 0x62, 0xfe, 0xb6, 0xc1, 0x6d, 0xd4, 0x20, 0x66, 0x54, 0xbb, 0x46, 0x34, 0xb3, 0x9c, 0x3b, 0x7d, - 0x51, 0xd9, 0x38, 0x93, 0x4d, 0x9b, 0xe4, 0xd4, 0x0e, 0x97, 0xdc, 0x1f, 0x7b, 0xb4, 0x8b, 0x48, - 0xf2, 0x3b, 0x09, 0x5a, 0xd1, 0xb7, 0x34, 0x68, 0xb4, 0xdf, 0xa7, 0x66, 0xc8, 0x5d, 0x3b, 0x53, - 0x19, 0xb4, 0x5b, 0x5c, 0xa8, 0x0b, 0xc2, 0xda, 0x63, 0xa8, 0xb4, 0xe8, 0x36, 0x13, 0x17, 0x07, - 0x04, 0x6b, 0x1b, 0x35, 0xf8, 0xdd, 0x62, 0xb0, 0x1f, 0xbb, 0xb4, 0x3e, 0x5e, 0x88, 0x0b, 0xdf, - 0x8b, 0x64, 0xf1, 0x79, 0x24, 0x9e, 0xe4, 0x80, 0xad, 0xf0, 0x15, 0xde, 0xf4, 0x1e, 0x4a, 0x17, - 0x3b, 0x6c, 0x09, 0x3e, 0xbc, 0x11, 0x2b, 0x4a, 0xd7, 0xe7, 0xf9, 0xca, 0xf2, 0xe6, 0x3d, 0x46, - 0xd8, 0xe5, 0x03, 0xc3, 0x6f, 0x14, 0xf0, 0x25, 0xea, 0x60, 0x19, 0x5f, 0x4e, 0xaa, 0x81, 0xe6, - 0x62, 0xb2, 0x30, 0x7f, 0x4c, 0xb4, 0xdf, 0x2b, 0xe0, 0xb5, 0x04, 0x0e, 0xbc, 0xde, 0x2b, 0x41, - 0xf7, 0x79, 0x3a, 0x23, 0x7e, 0x2b, 0x86, 0x27, 0x9a, 0xf4, 0x02, 0x6f, 0xd2, 0x35, 0xf1, 0x43, - 0x49, 0xfc, 0x02, 0xed, 0x2f, 0x0a, 0x7a, 0x31, 0x9b, 0x99, 0x3b, 0xd4, 0xe9, 0x51, 0x3f, 0x0e, - 0xe3, 0x75, 0xb4, 0xe4, 0xf0, 0x85, 0xda, 0x6a, 0x00, 0xb9, 0x63, 0x04, 0x2c, 0x57, 0x44, 0x8b, - 0xf9, 0x22, 0xa2, 0x70, 0xd6, 0x0b, 0x50, 0x21, 0xa8, 0xb7, 0xd0, 0xaa, 0x50, 0x97, 0x10, 0xe7, - 0xba, 0xb0, 0x74, 0x28, 0x64, 0x0b, 0x02, 0xb1, 0x78, 0xd0, 0xfa, 0x30, 0x28, 0x26, 0xbd, 0x2a, - 0x53, 0x57, 0xb3, 0x9a, 0xe5, 0x35, 0x84, 0xd3, 0x66, 0x09, 0x69, 0x89, 0x6f, 0xdd, 0xb4, 0x27, - 0x8a, 0x44, 0x58, 0xda, 0x3e, 0x44, 0x3e, 0xbf, 0xcf, 0xf1, 0x3a, 0xe2, 0x4d, 0xa8, 0x39, 0xb1, - 0x9c, 0x1b, 0x71, 0x85, 0x8c, 0x34, 0xe2, 0x8a, 0x85, 0x8e, 0xa5, 0xdd, 0x87, 0xc9, 0x48, 0x56, - 0x3b, 0x1e, 0x90, 0xf7, 0x15, 0xa0, 0x62, 0xb7, 0x99, 0x79, 0xb8, 0x4b, 0x69, 0x7a, 0x30, 0xa3, - 0x20, 0x39, 0xc4, 0x1f, 0x1b, 0x81, 0x97, 0x5c, 0x29, 0xca, 0x1c, 0x57, 0x4a, 0xa4, 0xf3, 0xc0, - 0x83, 0xf5, 0xc8, 0x1d, 0xd3, 0xa7, 0x24, 0xa4, 0x06, 0x09, 0x79, 0x8c, 0x17, 0xbb, 0xcb, 0x62, - 0x61, 0x27, 0xc4, 0x97, 0xd0, 0xaa, 0x47, 0xc6, 0x43, 0x46, 0x2c, 0x23, 0xb0, 0xdf, 0x11, 0xb5, - 0x74, 0xba, 0xdb, 0x80, 0xb5, 0x07, 0xf6, 0x3b, 0x54, 0x1b, 0xa2, 0xb5, 0x2c, 0x3c, 0x70, 0x77, - 0x1f, 0x2d, 0x11, 0x27, 0xba, 0x9b, 0x00, 0xd3, 0x1b, 0x11, 0xe7, 0xfa, 0xe4, 0xd3, 0x0b, 0x97, - 0x07, 0x76, 0xf8, 0x68, 0xd4, 0x6b, 0x99, 0xcc, 0x01, 0xa6, 0x0d, 0x7f, 0x36, 0x03, 0xeb, 0x10, - 0x98, 0x69, 0xc7, 0x0d, 0x3f, 0xfe, 0x60, 0x13, 0x81, 0x07, 0x1d, 0x37, 0xec, 0x82, 0xad, 0x1b, - 0xef, 0xab, 0xe8, 0x0b, 0x7c, 0x3b, 0x3c, 0x45, 0x4b, 0x82, 0xcd, 0xe1, 0xcb, 0x65, 0xc7, 0xb4, - 0xc8, 0x69, 0xd5, 0x2b, 0xb5, 0x72, 0x02, 0xba, 0xa6, 0xbd, 0xfb, 0x9f, 0xff, 0xff, 0x6a, 0x61, - 0x1d, 0xab, 0x7a, 0x25, 0x03, 0xc7, 0x7f, 0x8b, 0x9b, 0x52, 0x81, 0x91, 0xe2, 0xad, 0x9a, 0x7d, - 0x8a, 0xe4, 0x57, 0xbd, 0x71, 0x14, 0x15, 0x40, 0xd9, 0xe2, 0x28, 0x37, 0xf0, 0xe5, 0x6a, 0x94, - 0xfa, 0x24, 0x61, 0xd0, 0x53, 0xfc, 0x5b, 0x05, 0xa1, 0x94, 0x6c, 0xe2, 0xab, 0x95, 0x5b, 0x16, - 0x78, 0xb0, 0xfa, 0xca, 0x5c, 0xb2, 0x80, 0xeb, 0x26, 0xc7, 0xa5, 0xe3, 0xcd, 0x32, 0x5c, 0x8f, - 0x28, 0xb1, 0x0c, 0x31, 0x00, 0xe8, 0x13, 0x69, 0x36, 0x98, 0xe2, 0x3f, 0x2b, 0xe8, 0x4c, 0x96, - 0x46, 0xe3, 0xd6, 0x1c, 0xdb, 0x4a, 0x27, 0xf3, 0x68, 0x30, 0xb7, 0x39, 0xcc, 0x57, 0xf1, 0x56, - 0x0d, 0x4c, 0xa3, 0x17, 0x1d, 0xf4, 0x04, 0xac, 0x6d, 0x4d, 0xf1, 0x7b, 0x0a, 0xfa, 0x52, 0x6a, - 0xf1, 0xee, 0xee, 0x3e, 0x7e, 0xa9, 0x72, 0xe7, 0x74, 0xd4, 0x56, 0xab, 0x23, 0x5e, 0x98, 0xb0, - 0xb5, 0x6f, 0x72, 0x74, 0xd7, 0x71, 0xab, 0x0e, 0x9d, 0xdb, 0x0f, 0xf5, 0x49, 0x3c, 0xc1, 0x4f, - 0xf1, 0x5f, 0x21, 0xc9, 0x62, 0x3c, 0xae, 0x49, 0x72, 0xe6, 0xd3, 0x40, 0x4d, 0xf4, 0xb2, 0x74, - 0x5d, 0xfb, 0x0e, 0xc7, 0xf7, 0x26, 0x7e, 0xbd, 0x12, 0x9f, 0x18, 0xe2, 0xb2, 0x49, 0xd6, 0x27, - 0xd2, 0xb4, 0x97, 0xa6, 0x3c, 0xfd, 0x8c, 0x50, 0x93, 0xf2, 0xc2, 0xf7, 0x86, 0xa3, 0x81, 0xae, - 0x4f, 0x39, 0xc0, 0x83, 0x94, 0x27, 0x5f, 0x32, 0xd2, 0x94, 0x27, 0x84, 0xe5, 0xb8, 0x29, 0x2f, - 0x30, 0x9f, 0x39, 0x52, 0x1e, 0x07, 0x2f, 0x9b, 0xf2, 0x5f, 0x2a, 0xa8, 0x21, 0x7d, 0x31, 0xc0, - 0xd5, 0x21, 0x29, 0x7e, 0xbb, 0x50, 0xaf, 0xcd, 0x27, 0x0c, 0x10, 0x37, 0x38, 0x44, 0x0d, 0x5f, - 0x2c, 0x83, 0x38, 0xb4, 0x83, 0x10, 0xaa, 0x32, 0xc0, 0xbf, 0x03, 0x50, 0xc0, 0x86, 0x6b, 0x40, - 0x65, 0xbf, 0x21, 0xd4, 0x80, 0xca, 0x11, 0xec, 0xd9, 0x71, 0xe3, 0xa0, 0x44, 0xdc, 0x82, 0x5c, - 0xc3, 0xf9, 0x87, 0x82, 0x9e, 0x2f, 0xfd, 0x76, 0x80, 0x6f, 0xce, 0xb3, 0x7f, 0xe1, 0x5b, 0xc3, - 0x11, 0x61, 0xef, 0x70, 0xd8, 0xaf, 0xe3, 0xed, 0x3a, 0xd8, 0x51, 0x35, 0x26, 0xcd, 0x27, 0xd3, - 0x87, 0x7e, 0xad, 0xa0, 0xd5, 0x64, 0x88, 0x9b, 0xbb, 0x26, 0x5f, 0xae, 0x14, 0xca, 0x53, 0xe6, - 0x39, 0x5a, 0x39, 0xcc, 0x99, 0xd9, 0x8a, 0xfc, 0x57, 0xcc, 0x26, 0xf2, 0x34, 0x15, 0x5f, 0xaf, - 0xbe, 0xe7, 0xca, 0x49, 0xb5, 0xba, 0x75, 0x04, 0x0d, 0x40, 0x7d, 0x87, 0xa3, 0xde, 0xc3, 0xb7, - 0x4a, 0x2f, 0x46, 0x31, 0xba, 0xf5, 0x99, 0x6f, 0x10, 0xa1, 0xa7, 0x4f, 0xe2, 0xc1, 0x73, 0xaa, - 0x4f, 0x0a, 0x24, 0x7d, 0x8a, 0xff, 0xad, 0xa0, 0xe7, 0xf2, 0xd4, 0x71, 0x86, 0x23, 0x15, 0x0c, - 0x7a, 0x86, 0x23, 0x55, 0xbc, 0x54, 0xdb, 0xe7, 0x8e, 0xdc, 0xc5, 0xb7, 0xcb, 0x1c, 0x79, 0xc2, - 0xb5, 0x0c, 0xe9, 0x7f, 0x07, 0x93, 0x98, 0x77, 0x4f, 0xf3, 0x5d, 0x57, 0xa2, 0xd0, 0x53, 0xfc, - 0x47, 0x05, 0xad, 0x24, 0x55, 0x83, 0x5f, 0x9e, 0xd9, 0x40, 0xe5, 0x91, 0x5d, 0xbd, 0x3a, 0x8f, - 0xe8, 0x3c, 0xd5, 0x9d, 0x56, 0x8e, 0x3e, 0x91, 0x38, 0xce, 0x34, 0x7e, 0x12, 0xe7, 0xf3, 0x3d, - 0x05, 0xad, 0x24, 0x8c, 0x6f, 0x06, 0xce, 0x3c, 0x65, 0x9d, 0x81, 0xb3, 0x40, 0x20, 0xb5, 0xd7, - 0x38, 0xce, 0x16, 0xbe, 0x56, 0x79, 0x0a, 0x4b, 0x70, 0xe2, 0x3f, 0x28, 0xe8, 0x6c, 0x8e, 0x3d, - 0x61, 0xbd, 0x3e, 0x3a, 0x19, 0x4a, 0xa8, 0x5e, 0x9f, 0x5f, 0x01, 0xc0, 0x6e, 0x72, 0xb0, 0x57, - 0xf0, 0x37, 0x6a, 0x8e, 0x23, 0x30, 0xc8, 0x7f, 0xc6, 0xcc, 0x21, 0xcb, 0x8c, 0x66, 0xdc, 0xb1, - 0xa5, 0x54, 0x4d, 0xd5, 0xe7, 0x96, 0x07, 0x9c, 0xb7, 0x39, 0xce, 0x5d, 0xfc, 0x76, 0xcd, 0x01, - 0x84, 0xd0, 0x96, 0x1e, 0xbf, 0x98, 0xf6, 0x4d, 0xa3, 0xab, 0xe4, 0x6c, 0x8e, 0x53, 0xcd, 0x98, - 0x6b, 0x0a, 0x7c, 0x6d, 0xc6, 0x88, 0x50, 0x24, 0x69, 0xb3, 0xeb, 0x01, 0xa0, 0xc3, 0x74, 0x90, - 0x90, 0xc0, 0x29, 0xfe, 0x99, 0x82, 0x56, 0x65, 0x12, 0x84, 0xab, 0xa9, 0x46, 0x96, 0xc5, 0xa9, - 0x1b, 0xf5, 0x82, 0x80, 0xec, 0xeb, 0x1c, 0x59, 0x13, 0xaf, 0x97, 0x56, 0x2a, 0x33, 0x0f, 0x8d, - 0x3e, 0xa5, 0xed, 0xce, 0x87, 0x4f, 0x9b, 0xca, 0x47, 0x4f, 0x9b, 0xca, 0xff, 0x9e, 0x36, 0x95, - 0x5f, 0x3c, 0x6b, 0x9e, 0xfa, 0xe8, 0x59, 0xf3, 0xd4, 0x7f, 0x9f, 0x35, 0x4f, 0x1d, 0xe8, 0x12, - 0xef, 0xea, 0xb9, 0xbd, 0x4d, 0xf3, 0x11, 0xb1, 0x5d, 0xd9, 0xd6, 0x4f, 0xb2, 0xff, 0x43, 0xec, - 0x2d, 0xf1, 0xff, 0x0f, 0xbe, 0xfa, 0x59, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x18, 0x28, 0xf4, - 0x7d, 0x1d, 0x00, 0x00, + // 2290 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x5a, 0xdb, 0x6f, 0xdc, 0x58, + 0x19, 0xaf, 0x93, 0x6e, 0x9a, 0x9c, 0x09, 0x6d, 0x39, 0x9b, 0xdd, 0xa6, 0xd3, 0x66, 0xda, 0x7a, + 0xa1, 0xcd, 0x76, 0x9b, 0x71, 0x93, 0x36, 0x88, 0xd0, 0x6d, 0x51, 0xb2, 0x4d, 0xc2, 0x48, 0xbd, + 0x04, 0x37, 0x0a, 0xa2, 0x12, 0xb2, 0xce, 0xd8, 0x67, 0xa6, 0xde, 0xcc, 0xd8, 0xae, 0xed, 0x69, + 0x3a, 0x3b, 0x1a, 0x21, 0xf6, 0x01, 0x78, 0x44, 0x20, 0xa4, 0x15, 0x02, 0x09, 0x84, 0x40, 0xc0, + 0x0b, 0x02, 0xed, 0x0b, 0x4f, 0xbc, 0xf0, 0xb0, 0x12, 0x42, 0x5a, 0x2d, 0x2f, 0x68, 0x1f, 0x56, + 0xd0, 0xf2, 0x87, 0xac, 0x7c, 0xce, 0x67, 0xfb, 0xf8, 0x32, 0xe3, 0xc9, 0x66, 0x9e, 0x3a, 0x3e, + 0xfe, 0x2e, 0xbf, 0xef, 0x72, 0xbe, 0x73, 0xfc, 0x6b, 0x50, 0xa5, 0xe9, 0x52, 0x6a, 0x35, 0x4c, + 0xda, 0x32, 0x14, 0xcf, 0xb7, 0x5d, 0xd2, 0xa4, 0xca, 0xd3, 0x0e, 0x75, 0xbb, 0x55, 0xc7, 0xb5, + 0x7d, 0x1b, 0xe3, 0xf8, 0x7d, 0x15, 0xde, 0x97, 0xaf, 0xea, 0xb6, 0xd7, 0xb6, 0x3d, 0xa5, 0x4e, + 0x3c, 0x10, 0x56, 0x9e, 0x2d, 0xd7, 0xa9, 0x4f, 0x96, 0x15, 0x87, 0x34, 0x4d, 0x8b, 0xf8, 0xa6, + 0x6d, 0x71, 0xfd, 0xf2, 0x59, 0x2e, 0xab, 0xb1, 0x27, 0x85, 0x3f, 0xc0, 0xab, 0xb9, 0xa6, 0xdd, + 0xb4, 0xf9, 0x7a, 0xf0, 0x0b, 0x56, 0xcf, 0x37, 0x6d, 0xbb, 0xd9, 0xa2, 0x0a, 0x71, 0x4c, 0x85, + 0x58, 0x96, 0xed, 0x33, 0x6b, 0xa1, 0x8e, 0x2c, 0xc0, 0x75, 0xa8, 0xdb, 0x36, 0x3d, 0xcf, 0xb4, + 0x2d, 0x45, 0xb7, 0xdb, 0xed, 0xc8, 0xe5, 0xa5, 0x7c, 0x19, 0xbf, 0xeb, 0xd0, 0xd0, 0xcc, 0x85, + 0x9c, 0xa8, 0x1d, 0xe2, 0x92, 0x76, 0x28, 0x90, 0x97, 0x16, 0xd1, 0xc0, 0x1b, 0xc2, 0xfb, 0x67, + 0xa6, 0xeb, 0x77, 0x48, 0xab, 0xe9, 0xda, 0x1d, 0x47, 0x14, 0x92, 0xe7, 0x10, 0xfe, 0x76, 0x90, + 0x9d, 0x1d, 0x66, 0x59, 0xa5, 0x4f, 0x3b, 0xd4, 0xf3, 0xe5, 0x87, 0xe8, 0xd5, 0xc4, 0xaa, 0xe7, + 0xd8, 0x96, 0x47, 0xf1, 0xd7, 0xd1, 0x14, 0x47, 0x30, 0x2f, 0x5d, 0x94, 0x16, 0x4b, 0x2b, 0xe5, + 0x6a, 0x36, 0xf3, 0x55, 0xae, 0xb3, 0x71, 0xfc, 0xa3, 0xcf, 0x2e, 0x1c, 0x53, 0x41, 0x5e, 0xbe, + 0x8d, 0x16, 0x04, 0x83, 0x1b, 0xdd, 0x5d, 0xb3, 0x4d, 0x3d, 0x9f, 0xb4, 0x1d, 0xf0, 0x88, 0xcf, + 0xa3, 0x19, 0x3f, 0x5c, 0x63, 0xd6, 0x27, 0xd5, 0x78, 0x41, 0x7e, 0x8c, 0x2a, 0x83, 0xd4, 0x8f, + 0x0c, 0x6d, 0x0d, 0xbd, 0xce, 0x6c, 0x7f, 0x8b, 0x12, 0x63, 0xa3, 0xa3, 0xef, 0x53, 0x3f, 0xc4, + 0x74, 0x01, 0x95, 0xea, 0x6c, 0x41, 0xb3, 0x48, 0x9b, 0x32, 0xc3, 0x33, 0x2a, 0xe2, 0x4b, 0x0f, + 0x48, 0x9b, 0xca, 0x6b, 0xa8, 0x9c, 0x52, 0xdd, 0xe8, 0xd6, 0x8c, 0x50, 0xfd, 0x1c, 0x9a, 0x01, + 0x75, 0xd3, 0x00, 0xe5, 0x69, 0xbe, 0x50, 0x33, 0xe4, 0xc7, 0xe8, 0x4c, 0xc6, 0x2b, 0x84, 0xf2, + 0xcd, 0xc8, 0xad, 0x69, 0x35, 0x6c, 0x88, 0xa7, 0x92, 0x17, 0x0f, 0x57, 0xac, 0x59, 0x0d, 0x3b, + 0x84, 0x15, 0xfc, 0x96, 0x1f, 0x0b, 0x11, 0x3d, 0xac, 0xbf, 0x4b, 0xf5, 0x91, 0x23, 0x0a, 0x04, + 0x6c, 0xa6, 0xc1, 0x05, 0x26, 0xb8, 0x00, 0x5f, 0xca, 0x84, 0xcc, 0x6d, 0xa7, 0x42, 0x06, 0xf5, + 0x38, 0x64, 0xbe, 0x50, 0x33, 0xe4, 0xbf, 0x49, 0x42, 0xcc, 0x21, 0xae, 0x38, 0xe6, 0x50, 0xb1, + 0x20, 0x66, 0xae, 0xc8, 0x63, 0xb6, 0xa3, 0xdf, 0xf8, 0x7b, 0x68, 0xae, 0xd9, 0xb2, 0xeb, 0xa4, + 0xa5, 0x41, 0xab, 0x6b, 0xac, 0xd7, 0x59, 0x04, 0xa5, 0x95, 0xb7, 0x44, 0x4b, 0xe2, 0x5e, 0xa8, + 0x6e, 0x33, 0xa5, 0x3d, 0xbe, 0xb4, 0x1d, 0x2c, 0xa9, 0xb8, 0x99, 0x59, 0x93, 0x09, 0x40, 0xbf, + 0x67, 0x7a, 0x3e, 0xcf, 0x7a, 0xb8, 0x57, 0xf0, 0x16, 0x42, 0xf1, 0x44, 0x01, 0xe4, 0x97, 0xab, + 0x30, 0x45, 0x82, 0xf1, 0x53, 0xe5, 0xb3, 0x0a, 0xc6, 0x4f, 0x75, 0x87, 0x34, 0x29, 0xe8, 0xaa, + 0x82, 0xa6, 0xfc, 0x7b, 0x09, 0xcd, 0x67, 0x7d, 0x40, 0x7e, 0xd6, 0xd1, 0xac, 0xd0, 0x13, 0x41, + 0x93, 0x4f, 0x8e, 0xd0, 0x14, 0xa5, 0xb8, 0x29, 0x3c, 0xbc, 0x9d, 0xc0, 0xc9, 0xf3, 0x72, 0xa5, + 0x10, 0x27, 0xf7, 0x9f, 0x00, 0xfa, 0xbe, 0x24, 0x24, 0x83, 0x97, 0x63, 0xdc, 0xc9, 0x48, 0x37, + 0xea, 0x44, 0x66, 0xeb, 0xfd, 0x58, 0x42, 0x97, 0xd2, 0x20, 0x36, 0xba, 0x10, 0xbb, 0x31, 0x6e, + 0x38, 0x89, 0xad, 0x3c, 0x91, 0xda, 0xca, 0x89, 0xc2, 0x45, 0xf9, 0x88, 0x0b, 0x27, 0x34, 0xf6, + 0xd0, 0xc2, 0x09, 0x9d, 0x5d, 0x8a, 0x3b, 0x7b, 0x8c, 0x85, 0xbb, 0x86, 0x4e, 0x31, 0x9c, 0x0f, + 0xb6, 0x76, 0xc3, 0x04, 0x9d, 0x45, 0xd3, 0xbe, 0xbd, 0x4f, 0xad, 0x78, 0xbf, 0x9e, 0x60, 0xcf, + 0x35, 0x43, 0xfe, 0x2e, 0x4c, 0x11, 0x9e, 0x53, 0xa6, 0x13, 0x6d, 0xd6, 0x99, 0x36, 0xf5, 0x89, + 0x66, 0x10, 0x9f, 0x40, 0x52, 0xe5, 0xc1, 0x9d, 0x78, 0x9f, 0xfa, 0xe4, 0x2e, 0xf1, 0x89, 0x3a, + 0xdd, 0x86, 0x5f, 0x91, 0x69, 0x1e, 0xf1, 0x17, 0x31, 0xcd, 0x35, 0x73, 0x4c, 0x7f, 0x07, 0xbd, + 0xc6, 0x4c, 0xb3, 0x6d, 0x2b, 0x5a, 0xbe, 0x93, 0xb5, 0x7c, 0x29, 0xcf, 0x32, 0x53, 0xcc, 0x31, + 0xfc, 0x03, 0x09, 0x9d, 0xe7, 0x67, 0x90, 0xdd, 0x32, 0xf5, 0xee, 0x96, 0xed, 0xae, 0xeb, 0xba, + 0xdd, 0xb1, 0xa2, 0xd9, 0x5a, 0x46, 0xd3, 0x2e, 0xf5, 0xec, 0x8e, 0xab, 0x87, 0x83, 0x35, 0x7a, + 0xc6, 0x9b, 0xe8, 0xcb, 0x8e, 0x6b, 0x5a, 0xba, 0xe9, 0x90, 0x96, 0x46, 0x0c, 0xc3, 0xa5, 0x9e, + 0xc7, 0xfb, 0x68, 0x63, 0xfe, 0x93, 0x0f, 0x97, 0xe6, 0xa0, 0x98, 0xeb, 0xfc, 0xcd, 0x23, 0xdf, + 0x35, 0xad, 0xa6, 0x7a, 0x3a, 0x52, 0x81, 0x75, 0x79, 0x2f, 0x3c, 0x45, 0x33, 0x10, 0x20, 0xc8, + 0x55, 0x34, 0xe5, 0xb0, 0x77, 0x10, 0xe1, 0x82, 0x18, 0x61, 0x7c, 0xcf, 0xa8, 0x72, 0x03, 0x2a, + 0x08, 0xcb, 0x9f, 0x86, 0xb1, 0xed, 0x51, 0xd7, 0x6c, 0x74, 0x77, 0x22, 0xc1, 0x30, 0xb6, 0x9b, + 0x68, 0xda, 0x76, 0xa8, 0x4b, 0x7c, 0xdb, 0xe5, 0xb1, 0x0d, 0x81, 0x1d, 0x49, 0x16, 0x6e, 0xe2, + 0xf4, 0x69, 0x33, 0x99, 0x3e, 0x6d, 0xf0, 0x06, 0x2a, 0x11, 0x3d, 0xe8, 0x5d, 0x2d, 0xb8, 0xb3, + 0xcc, 0x1f, 0xbf, 0x28, 0x2d, 0x9e, 0x4c, 0x96, 0x4d, 0x08, 0x6a, 0x9d, 0x49, 0xee, 0x76, 0x1d, + 0xaa, 0x22, 0x12, 0xfd, 0x8e, 0x92, 0x96, 0x8d, 0x2d, 0x4e, 0x1a, 0x6d, 0x34, 0xa8, 0xee, 0xb3, + 0xd0, 0x4e, 0x0e, 0x4c, 0xda, 0x26, 0x13, 0x52, 0x41, 0x58, 0x7e, 0x0a, 0x9d, 0x16, 0x9c, 0x66, + 0xfc, 0xe0, 0x80, 0x64, 0xad, 0xa1, 0x12, 0x3b, 0x5b, 0x34, 0xfb, 0xc0, 0xa2, 0xc5, 0xf9, 0x42, + 0x4c, 0xf8, 0x61, 0x20, 0x8b, 0x17, 0x10, 0x7f, 0x12, 0x13, 0x36, 0xc3, 0x56, 0xd8, 0xd0, 0xdb, + 0x13, 0x0e, 0x76, 0x70, 0x09, 0x31, 0xbc, 0x1d, 0x2a, 0x0a, 0xc7, 0xe7, 0xc2, 0xc0, 0xf6, 0x66, + 0x33, 0x86, 0xdb, 0x65, 0x17, 0x86, 0x5f, 0x48, 0x10, 0x4b, 0x30, 0xc1, 0x12, 0xb1, 0x8c, 0x6b, + 0x80, 0xa6, 0x72, 0x32, 0x31, 0x7a, 0x4e, 0xe4, 0xdf, 0x48, 0x10, 0xb5, 0x00, 0x0e, 0xa2, 0xde, + 0xce, 0x41, 0xf7, 0x45, 0x26, 0x23, 0xbe, 0x13, 0xc2, 0xe3, 0x43, 0x7a, 0x82, 0x0d, 0xe9, 0x82, + 0xfc, 0xa1, 0x28, 0x7f, 0x9e, 0xfc, 0x47, 0x09, 0x9d, 0x4b, 0x56, 0xe6, 0x3e, 0x6d, 0xd7, 0xa9, + 0x1b, 0xa6, 0xf1, 0x3a, 0x9a, 0x6a, 0xb3, 0x85, 0xc2, 0x6e, 0x00, 0xb9, 0x23, 0x24, 0x2c, 0xd5, + 0x44, 0x93, 0xe9, 0x26, 0xa2, 0xb0, 0xd7, 0x33, 0x50, 0x21, 0xa9, 0x9b, 0x68, 0x96, 0xab, 0x0b, + 0x88, 0x53, 0x53, 0x58, 0xd8, 0x14, 0xa2, 0x05, 0x8e, 0x98, 0x3f, 0xc8, 0x0d, 0xb8, 0x28, 0x46, + 0xb3, 0x2a, 0xd1, 0x57, 0xc3, 0x86, 0xe5, 0x35, 0x84, 0xe3, 0x61, 0x09, 0x65, 0x09, 0x4f, 0xdd, + 0x78, 0x26, 0xf2, 0x42, 0x18, 0xf2, 0x2e, 0x64, 0x3e, 0xed, 0xe7, 0x68, 0x13, 0x71, 0x15, 0x7a, + 0x8e, 0x2f, 0xa7, 0xae, 0xb8, 0x5c, 0x46, 0xb8, 0xe2, 0xf2, 0x85, 0x9a, 0x21, 0xef, 0xc0, 0xcd, + 0x48, 0x54, 0x3b, 0x1a, 0x90, 0x5f, 0x49, 0xf0, 0x29, 0x76, 0xcf, 0xd6, 0xf7, 0xb7, 0x28, 0x8d, + 0x37, 0x66, 0x90, 0xa4, 0x36, 0x71, 0xbb, 0x9a, 0xe7, 0x44, 0x47, 0x8a, 0x34, 0xc2, 0x91, 0x12, + 0xe8, 0x3c, 0x72, 0x60, 0x3d, 0x08, 0x47, 0x77, 0x29, 0xf1, 0xa9, 0x46, 0x7c, 0x96, 0xe3, 0x49, + 0x75, 0x9a, 0x2f, 0xac, 0xfb, 0xf8, 0x12, 0x9a, 0x75, 0x48, 0xb7, 0x65, 0x13, 0x43, 0xf3, 0xcc, + 0xf7, 0x78, 0x2f, 0x1d, 0x57, 0x4b, 0xb0, 0xf6, 0xc8, 0x7c, 0x8f, 0xca, 0x2d, 0x34, 0x97, 0x84, + 0x07, 0xe1, 0xee, 0xa2, 0x29, 0xd2, 0x0e, 0xce, 0x26, 0xc0, 0xf4, 0x76, 0xf0, 0xcd, 0xf5, 0xe9, + 0x67, 0x17, 0x2e, 0x37, 0x4d, 0xff, 0x49, 0xa7, 0x5e, 0xd5, 0xed, 0x36, 0x7c, 0x69, 0xc3, 0x3f, + 0x4b, 0x9e, 0xb1, 0x0f, 0x5f, 0xa6, 0x35, 0xcb, 0xff, 0xe4, 0xc3, 0x25, 0x04, 0x11, 0xd4, 0x2c, + 0x5f, 0x05, 0x5b, 0xf2, 0x1d, 0x61, 0x9b, 0xf1, 0xdb, 0xc5, 0xe6, 0x73, 0xdf, 0x25, 0x23, 0x7f, + 0xb0, 0x89, 0xbd, 0x9f, 0xd0, 0x8f, 0x7a, 0x1f, 0xd1, 0x60, 0x41, 0x1c, 0xa3, 0x97, 0xf3, 0xc6, + 0x40, 0xcd, 0xf2, 0xa9, 0x6b, 0x91, 0x96, 0x70, 0xd9, 0x9e, 0x61, 0x9a, 0x6c, 0x9e, 0xde, 0x86, + 0xde, 0xaf, 0x79, 0x3b, 0xae, 0xa9, 0xd3, 0x77, 0x9e, 0x10, 0xab, 0x49, 0x8d, 0x91, 0x51, 0xfe, + 0xef, 0x04, 0x84, 0x99, 0xd6, 0x07, 0x94, 0xf3, 0xe8, 0x84, 0xce, 0x97, 0x98, 0xf2, 0xb4, 0x1a, + 0x3e, 0xe2, 0x77, 0x11, 0xd6, 0x3b, 0xae, 0x4b, 0x2d, 0x5f, 0x73, 0x29, 0x31, 0x34, 0x27, 0x50, + 0x87, 0xe1, 0x71, 0x98, 0x0a, 0xdc, 0xa5, 0xba, 0x50, 0x81, 0xbb, 0x54, 0x57, 0x4f, 0x83, 0x5d, + 0x95, 0x12, 0x83, 0x81, 0xc2, 0x3d, 0x74, 0x2e, 0xf4, 0x15, 0x75, 0xa2, 0x6f, 0xbb, 0x14, 0x9c, + 0x4e, 0x8e, 0xc1, 0xe9, 0x3c, 0x38, 0xd8, 0x81, 0xae, 0x0d, 0xcc, 0x73, 0xe7, 0xdf, 0x47, 0x0b, + 0xa1, 0x73, 0x8f, 0xea, 0xb6, 0x65, 0xa4, 0xdd, 0x1f, 0x1f, 0x83, 0xfb, 0x32, 0xb8, 0x78, 0x14, + 0x7a, 0x10, 0x00, 0x74, 0x51, 0xf8, 0x56, 0x7b, 0x46, 0x5a, 0xa6, 0x11, 0x5c, 0x78, 0x34, 0x9f, + 0x3c, 0xd7, 0x5c, 0xe2, 0xd3, 0xf9, 0x57, 0xc6, 0xe0, 0xfd, 0x0c, 0xd8, 0xdf, 0x0b, 0xcd, 0xef, + 0x92, 0xe7, 0x2a, 0xf1, 0x29, 0xae, 0xa3, 0x93, 0x16, 0x3d, 0x10, 0x0b, 0x3c, 0x35, 0x06, 0x77, + 0xb3, 0x16, 0x3d, 0x88, 0x8b, 0xeb, 0xa1, 0x33, 0x81, 0x8f, 0xbc, 0xc2, 0x9e, 0x18, 0x83, 0xb3, + 0x39, 0x8b, 0x1e, 0x64, 0x8b, 0x7a, 0x80, 0xce, 0x06, 0x4e, 0xf3, 0x0b, 0x3a, 0x3d, 0x06, 0xb7, + 0xaf, 0x5b, 0xf4, 0x20, 0xaf, 0x98, 0x4f, 0x51, 0xf0, 0x26, 0xaf, 0x90, 0x33, 0x63, 0xf0, 0xfa, + 0xaa, 0x45, 0x0f, 0xd2, 0x45, 0x5c, 0xf9, 0xe1, 0x02, 0x7a, 0x85, 0xed, 0x71, 0xdc, 0x47, 0x53, + 0x9c, 0x97, 0xc2, 0xb9, 0x93, 0x26, 0xcb, 0xce, 0x95, 0xaf, 0x14, 0xca, 0xf1, 0x41, 0x21, 0xcb, + 0xef, 0xff, 0xfb, 0xff, 0x3f, 0x9b, 0x38, 0x8f, 0xcb, 0xca, 0x40, 0x2e, 0x11, 0xff, 0x39, 0xbc, + 0x5e, 0x65, 0xb8, 0x35, 0xbc, 0x5c, 0xe0, 0x27, 0x4b, 0xe3, 0x95, 0x57, 0x0e, 0xa3, 0x02, 0x28, + 0xab, 0x0c, 0xe5, 0x22, 0xbe, 0x3c, 0x18, 0xa5, 0xd2, 0x8b, 0xb8, 0xc0, 0x3e, 0xfe, 0xa5, 0x84, + 0x50, 0x3c, 0xc0, 0xf1, 0xd5, 0x81, 0x2e, 0x33, 0x8c, 0x5e, 0xf9, 0xad, 0x91, 0x64, 0x01, 0xd7, + 0x2a, 0xc3, 0xa5, 0xe0, 0xa5, 0x3c, 0x5c, 0x4f, 0x82, 0xdd, 0xc7, 0x67, 0xb6, 0xd2, 0x13, 0xc6, + 0x79, 0x1f, 0xff, 0x41, 0x42, 0x27, 0x93, 0x84, 0x20, 0xae, 0x8e, 0xe0, 0x56, 0xb8, 0x63, 0x1c, + 0x0e, 0xe6, 0x1a, 0x83, 0x79, 0x03, 0x2f, 0x17, 0xc0, 0xd4, 0xea, 0xc1, 0x95, 0x25, 0x02, 0x6b, + 0x1a, 0x7d, 0xfc, 0x81, 0x84, 0xbe, 0x14, 0x5b, 0x7c, 0xb0, 0xb5, 0x8b, 0xdf, 0x18, 0xe8, 0x39, + 0x26, 0x0d, 0xca, 0x83, 0x33, 0x9e, 0xe1, 0x0a, 0xe4, 0xaf, 0x31, 0x74, 0xd7, 0x71, 0xb5, 0x08, + 0x9d, 0xd5, 0xf0, 0x95, 0x5e, 0xc8, 0x45, 0xf4, 0xf1, 0x9f, 0xa0, 0xc8, 0xfc, 0x43, 0xbf, 0xa0, + 0xc8, 0x09, 0x92, 0xb3, 0x20, 0x7b, 0x49, 0xe2, 0x51, 0x7e, 0x87, 0xe1, 0xbb, 0x8d, 0x6f, 0x0d, + 0xc4, 0xc7, 0x3f, 0x47, 0x93, 0x45, 0x56, 0x7a, 0xc2, 0x77, 0x6b, 0x5c, 0xf2, 0x98, 0x10, 0x2d, + 0x28, 0x79, 0x86, 0x39, 0x3d, 0x1c, 0xe8, 0xe2, 0x92, 0x03, 0x3c, 0x28, 0x79, 0xc4, 0xc9, 0xc6, + 0x25, 0x8f, 0xa8, 0x97, 0xa3, 0x96, 0x3c, 0xc3, 0xe1, 0x8c, 0x50, 0xf2, 0x30, 0x79, 0xc9, 0x92, + 0xff, 0x54, 0x42, 0x25, 0x81, 0xfb, 0xc4, 0x83, 0x53, 0x92, 0x65, 0x61, 0xcb, 0xd7, 0x46, 0x13, + 0x06, 0x88, 0x8b, 0x0c, 0xa2, 0x8c, 0x2f, 0xe6, 0x41, 0x6c, 0x99, 0x9e, 0x0f, 0x5d, 0xe9, 0xe1, + 0x5f, 0x03, 0x28, 0xe0, 0xf5, 0x0a, 0x40, 0x25, 0xd9, 0xd0, 0x02, 0x50, 0x29, 0xaa, 0x70, 0x78, + 0xde, 0x18, 0x28, 0x9e, 0x37, 0x2f, 0x35, 0x70, 0xfe, 0x2e, 0xa1, 0xd7, 0x72, 0x59, 0x50, 0xbc, + 0x3a, 0x8a, 0xff, 0x0c, 0x6b, 0x7a, 0x48, 0xd8, 0xeb, 0x0c, 0xf6, 0x2d, 0xbc, 0x56, 0x04, 0x3b, + 0xe8, 0xc6, 0x68, 0xf8, 0x24, 0xe6, 0xd0, 0xcf, 0x25, 0x34, 0x1b, 0x7d, 0x8e, 0x8e, 0xdc, 0x93, + 0x6f, 0x0e, 0x14, 0x4a, 0x93, 0x7f, 0x23, 0x8c, 0x72, 0xf8, 0x62, 0x4e, 0x76, 0xe4, 0x3f, 0x43, + 0x5e, 0x24, 0x4d, 0xb8, 0xe1, 0xeb, 0x83, 0xcf, 0xb9, 0x7c, 0x7a, 0xb0, 0xbc, 0x7c, 0x08, 0x0d, + 0x40, 0x7d, 0x9f, 0xa1, 0xde, 0xc6, 0x9b, 0xb9, 0x07, 0x23, 0xff, 0x08, 0x6d, 0xd8, 0xae, 0x46, + 0xb8, 0x9e, 0xd2, 0x0b, 0x3f, 0xa1, 0xfb, 0x4a, 0x2f, 0x43, 0x37, 0xf6, 0xf1, 0xbf, 0x24, 0x74, + 0x3a, 0x4d, 0x82, 0x0d, 0x09, 0x64, 0x00, 0x17, 0x38, 0x24, 0x90, 0x41, 0x0c, 0x9b, 0xbc, 0xcb, + 0x02, 0x79, 0x80, 0xef, 0xe5, 0x05, 0xf2, 0x8c, 0x69, 0x69, 0xc2, 0xff, 0x82, 0xf6, 0x42, 0x06, + 0xb1, 0x9f, 0x9e, 0xba, 0x02, 0x19, 0xd8, 0xc7, 0xbf, 0x93, 0xd0, 0x4c, 0xd4, 0x35, 0xf8, 0xcd, + 0xa1, 0x03, 0x54, 0x24, 0x1f, 0xca, 0x57, 0x47, 0x11, 0x1d, 0xa5, 0xbb, 0xe3, 0xce, 0x51, 0x7a, + 0x02, 0x5b, 0xd3, 0x0f, 0x9f, 0xf8, 0xfe, 0xfc, 0x40, 0x42, 0x33, 0x11, 0x77, 0x35, 0x04, 0x67, + 0x9a, 0x7c, 0x1b, 0x82, 0x33, 0x43, 0x85, 0xc9, 0x37, 0x19, 0xce, 0x2a, 0xbe, 0x36, 0x70, 0x17, + 0xe6, 0xe0, 0xc4, 0xbf, 0x95, 0xd0, 0xa9, 0x14, 0x0f, 0x84, 0x95, 0xe2, 0xec, 0x24, 0xc8, 0xad, + 0xf2, 0xf5, 0xd1, 0x15, 0x00, 0xec, 0x12, 0x03, 0x7b, 0x05, 0x7f, 0xb5, 0x60, 0x3b, 0x02, 0x17, + 0xf6, 0x8f, 0x90, 0x03, 0x49, 0x72, 0x3c, 0x43, 0xce, 0xd8, 0x5c, 0xd2, 0xa9, 0xac, 0x8c, 0x2c, + 0x0f, 0x38, 0xef, 0x31, 0x9c, 0x5b, 0xf8, 0x6e, 0xc1, 0x06, 0x84, 0xd4, 0xe6, 0x6e, 0xbf, 0x90, + 0xc0, 0xea, 0x07, 0x47, 0xc9, 0xa9, 0x14, 0x3b, 0x34, 0xe4, 0x5e, 0x93, 0x61, 0x9e, 0x86, 0x5c, + 0x11, 0xb2, 0x74, 0xd3, 0xf0, 0x7e, 0x00, 0xe8, 0x70, 0x3b, 0x88, 0xe8, 0xac, 0x3e, 0xfe, 0x91, + 0x84, 0x66, 0x45, 0x3a, 0x07, 0x0f, 0xfe, 0xd4, 0x48, 0xf2, 0x51, 0xe5, 0xc5, 0x62, 0x41, 0x40, + 0xf6, 0x15, 0x86, 0xac, 0x82, 0xcf, 0xe7, 0x76, 0xaa, 0xad, 0xef, 0x6b, 0x0d, 0x4a, 0xf1, 0x5f, + 0xa0, 0x33, 0x05, 0x96, 0xa6, 0xa0, 0x33, 0xb3, 0x7c, 0x50, 0x41, 0x67, 0xe6, 0x10, 0x40, 0xf2, + 0x2d, 0x06, 0x6e, 0x15, 0xdf, 0x28, 0xba, 0xae, 0x32, 0xb2, 0x27, 0x75, 0x10, 0xff, 0x35, 0xec, + 0xd3, 0x24, 0x6f, 0x33, 0xa4, 0x4f, 0x73, 0x09, 0xa2, 0x21, 0x7d, 0x9a, 0x4f, 0x08, 0xc9, 0xdf, + 0x60, 0xa8, 0x6f, 0xe2, 0x95, 0x3c, 0xd4, 0xa6, 0xc7, 0xbf, 0xa0, 0x35, 0x20, 0x89, 0x92, 0xa0, + 0x37, 0x6a, 0x1f, 0xbd, 0xa8, 0x48, 0x1f, 0xbf, 0xa8, 0x48, 0xff, 0x7d, 0x51, 0x91, 0x7e, 0xf2, + 0xb2, 0x72, 0xec, 0xe3, 0x97, 0x95, 0x63, 0xff, 0x79, 0x59, 0x39, 0xf6, 0x58, 0x11, 0xbe, 0x76, + 0xeb, 0x56, 0x7d, 0x49, 0x7f, 0x42, 0x4c, 0x4b, 0xf4, 0xf0, 0x3c, 0xf9, 0x67, 0x27, 0xf5, 0x29, + 0xf6, 0x27, 0x25, 0x37, 0x3e, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x32, 0xdd, 0xcd, 0x59, 0xb0, 0x23, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1932,6 +2141,10 @@ type QueryClient interface { QueryPolicyById(ctx context.Context, in *QueryPolicyByIdRequest, opts ...grpc.CallOption) (*QueryPolicyByIdResponse, error) // Queries lock fee for storing an object QueryLockFee(ctx context.Context, in *QueryLockFeeRequest, opts ...grpc.CallOption) (*QueryLockFeeResponse, error) + // Queries a bucket extra info (with gvg bindings and price time) with specify name. + HeadBucketExtra(ctx context.Context, in *QueryHeadBucketExtraRequest, opts ...grpc.CallOption) (*QueryHeadBucketExtraResponse, error) + // Queries whether read and storage prices changed for the bucket. + QueryIsPriceChanged(ctx context.Context, in *QueryIsPriceChangedRequest, opts ...grpc.CallOption) (*QueryIsPriceChangedResponse, error) } type queryClient struct { @@ -2122,6 +2335,24 @@ func (c *queryClient) QueryLockFee(ctx context.Context, in *QueryLockFeeRequest, return out, nil } +func (c *queryClient) HeadBucketExtra(ctx context.Context, in *QueryHeadBucketExtraRequest, opts ...grpc.CallOption) (*QueryHeadBucketExtraResponse, error) { + out := new(QueryHeadBucketExtraResponse) + err := c.cc.Invoke(ctx, "/greenfield.storage.Query/HeadBucketExtra", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) QueryIsPriceChanged(ctx context.Context, in *QueryIsPriceChangedRequest, opts ...grpc.CallOption) (*QueryIsPriceChangedResponse, error) { + out := new(QueryIsPriceChangedResponse) + err := c.cc.Invoke(ctx, "/greenfield.storage.Query/QueryIsPriceChanged", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Parameters queries the parameters of the module. @@ -2164,6 +2395,10 @@ type QueryServer interface { QueryPolicyById(context.Context, *QueryPolicyByIdRequest) (*QueryPolicyByIdResponse, error) // Queries lock fee for storing an object QueryLockFee(context.Context, *QueryLockFeeRequest) (*QueryLockFeeResponse, error) + // Queries a bucket extra info (with gvg bindings and price time) with specify name. + HeadBucketExtra(context.Context, *QueryHeadBucketExtraRequest) (*QueryHeadBucketExtraResponse, error) + // Queries whether read and storage prices changed for the bucket. + QueryIsPriceChanged(context.Context, *QueryIsPriceChangedRequest) (*QueryIsPriceChangedResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -2230,6 +2465,12 @@ func (*UnimplementedQueryServer) QueryPolicyById(ctx context.Context, req *Query func (*UnimplementedQueryServer) QueryLockFee(ctx context.Context, req *QueryLockFeeRequest) (*QueryLockFeeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method QueryLockFee not implemented") } +func (*UnimplementedQueryServer) HeadBucketExtra(ctx context.Context, req *QueryHeadBucketExtraRequest) (*QueryHeadBucketExtraResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method HeadBucketExtra not implemented") +} +func (*UnimplementedQueryServer) QueryIsPriceChanged(ctx context.Context, req *QueryIsPriceChangedRequest) (*QueryIsPriceChangedResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method QueryIsPriceChanged not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -2595,6 +2836,42 @@ func _Query_QueryLockFee_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Query_HeadBucketExtra_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryHeadBucketExtraRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).HeadBucketExtra(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/greenfield.storage.Query/HeadBucketExtra", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).HeadBucketExtra(ctx, req.(*QueryHeadBucketExtraRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_QueryIsPriceChanged_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryIsPriceChangedRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).QueryIsPriceChanged(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/greenfield.storage.Query/QueryIsPriceChanged", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).QueryIsPriceChanged(ctx, req.(*QueryIsPriceChangedRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "greenfield.storage.Query", HandlerType: (*QueryServer)(nil), @@ -2679,6 +2956,14 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "QueryLockFee", Handler: _Query_QueryLockFee_Handler, }, + { + MethodName: "HeadBucketExtra", + Handler: _Query_HeadBucketExtra_Handler, + }, + { + MethodName: "QueryIsPriceChanged", + Handler: _Query_QueryIsPriceChanged_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "greenfield/storage/query.proto", @@ -3963,89 +4248,297 @@ func (m *QueryLockFeeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { - offset -= sovQuery(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *QueryParamsRequest) Size() (n int) { - if m == nil { - return 0 +func (m *QueryHeadBucketExtraRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - return n + return dAtA[:n], nil } -func (m *QueryParamsResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.Params.Size() - n += 1 + l + sovQuery(uint64(l)) - return n +func (m *QueryHeadBucketExtraRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryParamsByTimestampRequest) Size() (n int) { - if m == nil { - return 0 - } +func (m *QueryHeadBucketExtraRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if m.Timestamp != 0 { - n += 1 + sovQuery(uint64(m.Timestamp)) + if len(m.BucketName) > 0 { + i -= len(m.BucketName) + copy(dAtA[i:], m.BucketName) + i = encodeVarintQuery(dAtA, i, uint64(len(m.BucketName))) + i-- + dAtA[i] = 0xa } - return n + return len(dAtA) - i, nil } -func (m *QueryParamsByTimestampResponse) Size() (n int) { - if m == nil { - return 0 +func (m *QueryHeadBucketExtraResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - l = m.Params.Size() - n += 1 + l + sovQuery(uint64(l)) - return n + return dAtA[:n], nil } -func (m *QueryHeadBucketRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.BucketName) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } - return n +func (m *QueryHeadBucketExtraResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryHeadBucketByIdRequest) Size() (n int) { - if m == nil { - return 0 - } +func (m *QueryHeadBucketExtraResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.BucketId) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) + if m.ExtraInfo != nil { + { + size, err := m.ExtraInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa } - return n + return len(dAtA) - i, nil } -func (m *QueryHeadBucketResponse) Size() (n int) { - if m == nil { - return 0 +func (m *QueryIsPriceChangedRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryIsPriceChangedRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryIsPriceChangedRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BucketName) > 0 { + i -= len(m.BucketName) + copy(dAtA[i:], m.BucketName) + i = encodeVarintQuery(dAtA, i, uint64(len(m.BucketName))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryIsPriceChangedResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryIsPriceChangedResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryIsPriceChangedResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.NewValidatorTaxRate.Size() + i -= size + if _, err := m.NewValidatorTaxRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + { + size := m.NewSecondaryStorePrice.Size() + i -= size + if _, err := m.NewSecondaryStorePrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + { + size := m.NewPrimaryStorePrice.Size() + i -= size + if _, err := m.NewPrimaryStorePrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + { + size := m.NewReadPrice.Size() + i -= size + if _, err := m.NewReadPrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + { + size := m.CurrentValidatorTaxRate.Size() + i -= size + if _, err := m.CurrentValidatorTaxRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + { + size := m.CurrentSecondaryStorePrice.Size() + i -= size + if _, err := m.CurrentSecondaryStorePrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size := m.CurrentPrimaryStorePrice.Size() + i -= size + if _, err := m.CurrentPrimaryStorePrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size := m.CurrentReadPrice.Size() + i -= size + if _, err := m.CurrentReadPrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if m.Changed { + i-- + if m.Changed { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryParamsByTimestampRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Timestamp != 0 { + n += 1 + sovQuery(uint64(m.Timestamp)) + } + return n +} + +func (m *QueryParamsByTimestampResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryHeadBucketRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.BucketName) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryHeadBucketByIdRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.BucketId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryHeadBucketResponse) Size() (n int) { + if m == nil { + return 0 } var l int _ = l @@ -4492,6 +4985,73 @@ func (m *QueryLockFeeResponse) Size() (n int) { return n } +func (m *QueryHeadBucketExtraRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.BucketName) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryHeadBucketExtraResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ExtraInfo != nil { + l = m.ExtraInfo.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryIsPriceChangedRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.BucketName) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryIsPriceChangedResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Changed { + n += 2 + } + l = m.CurrentReadPrice.Size() + n += 1 + l + sovQuery(uint64(l)) + l = m.CurrentPrimaryStorePrice.Size() + n += 1 + l + sovQuery(uint64(l)) + l = m.CurrentSecondaryStorePrice.Size() + n += 1 + l + sovQuery(uint64(l)) + l = m.CurrentValidatorTaxRate.Size() + n += 1 + l + sovQuery(uint64(l)) + l = m.NewReadPrice.Size() + n += 1 + l + sovQuery(uint64(l)) + l = m.NewPrimaryStorePrice.Size() + n += 1 + l + sovQuery(uint64(l)) + l = m.NewSecondaryStorePrice.Size() + n += 1 + l + sovQuery(uint64(l)) + l = m.NewValidatorTaxRate.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -7929,6 +8489,598 @@ func (m *QueryLockFeeResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryHeadBucketExtraRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryHeadBucketExtraRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryHeadBucketExtraRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BucketName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BucketName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryHeadBucketExtraResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryHeadBucketExtraResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryHeadBucketExtraResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExtraInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ExtraInfo == nil { + m.ExtraInfo = &InternalBucketInfo{} + } + if err := m.ExtraInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryIsPriceChangedRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryIsPriceChangedRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryIsPriceChangedRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BucketName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BucketName = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryIsPriceChangedResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryIsPriceChangedResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryIsPriceChangedResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Changed", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Changed = bool(v != 0) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentReadPrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.CurrentReadPrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentPrimaryStorePrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.CurrentPrimaryStorePrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentSecondaryStorePrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.CurrentSecondaryStorePrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentValidatorTaxRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.CurrentValidatorTaxRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewReadPrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.NewReadPrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewPrimaryStorePrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.NewPrimaryStorePrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewSecondaryStorePrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.NewSecondaryStorePrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewValidatorTaxRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.NewValidatorTaxRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/storage/types/query.pb.gw.go b/x/storage/types/query.pb.gw.go index 8b05ed2f9..f66bbd84e 100644 --- a/x/storage/types/query.pb.gw.go +++ b/x/storage/types/query.pb.gw.go @@ -1234,6 +1234,114 @@ func local_request_Query_QueryLockFee_0(ctx context.Context, marshaler runtime.M } +func request_Query_HeadBucketExtra_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryHeadBucketExtraRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["bucket_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "bucket_name") + } + + protoReq.BucketName, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "bucket_name", err) + } + + msg, err := client.HeadBucketExtra(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_HeadBucketExtra_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryHeadBucketExtraRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["bucket_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "bucket_name") + } + + protoReq.BucketName, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "bucket_name", err) + } + + msg, err := server.HeadBucketExtra(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_QueryIsPriceChanged_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryIsPriceChangedRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["bucket_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "bucket_name") + } + + protoReq.BucketName, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "bucket_name", err) + } + + msg, err := client.QueryIsPriceChanged(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_QueryIsPriceChanged_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryIsPriceChangedRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["bucket_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "bucket_name") + } + + protoReq.BucketName, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "bucket_name", err) + } + + msg, err := server.QueryIsPriceChanged(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1700,6 +1808,52 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_HeadBucketExtra_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_HeadBucketExtra_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_HeadBucketExtra_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_QueryIsPriceChanged_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_QueryIsPriceChanged_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_QueryIsPriceChanged_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -2141,6 +2295,46 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_HeadBucketExtra_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_HeadBucketExtra_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_HeadBucketExtra_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_QueryIsPriceChanged_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_QueryIsPriceChanged_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_QueryIsPriceChanged_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -2184,6 +2378,10 @@ var ( pattern_Query_QueryPolicyById_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"greenfield", "storage", "policy_by_id", "policy_id"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_QueryLockFee_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"greenfield", "storage", "lock_fee"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_HeadBucketExtra_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"greenfield", "storage", "head_bucket_extra", "bucket_name"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_QueryIsPriceChanged_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"greenfield", "storage", "is_price_changed", "bucket_name"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -2226,4 +2424,8 @@ var ( forward_Query_QueryPolicyById_0 = runtime.ForwardResponseMessage forward_Query_QueryLockFee_0 = runtime.ForwardResponseMessage + + forward_Query_HeadBucketExtra_0 = runtime.ForwardResponseMessage + + forward_Query_QueryIsPriceChanged_0 = runtime.ForwardResponseMessage ) diff --git a/x/virtualgroup/keeper/keeper.go b/x/virtualgroup/keeper/keeper.go index 7033af62b..9e8fbf81d 100644 --- a/x/virtualgroup/keeper/keeper.go +++ b/x/virtualgroup/keeper/keeper.go @@ -126,7 +126,7 @@ func (k Keeper) DeleteGVG(ctx sdk.Context, primarySp *sptypes.StorageProvider, g gvgFamily.MustRemoveGVG(gvg.Id) // update stat - stat := k.MustGetGVGStatisticsWithinSP(ctx, gvgFamily.Id) + stat := k.MustGetGVGStatisticsWithinSP(ctx, gvgFamily.PrimarySpId) stat.PrimaryCount-- k.SetGVGStatisticsWithSP(ctx, stat) diff --git a/x/virtualgroup/keeper/payment_test.go b/x/virtualgroup/keeper/payment_test.go new file mode 100644 index 000000000..a8acb37f7 --- /dev/null +++ b/x/virtualgroup/keeper/payment_test.go @@ -0,0 +1,125 @@ +package keeper_test + +import ( + "errors" + "testing" + + "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/bnb-chain/greenfield/testutil/sample" + "github.com/bnb-chain/greenfield/x/challenge" + sptypes "github.com/bnb-chain/greenfield/x/sp/types" + "github.com/bnb-chain/greenfield/x/virtualgroup/keeper" + "github.com/bnb-chain/greenfield/x/virtualgroup/types" +) + +type TestSuite struct { + suite.Suite + + cdc codec.Codec + virtualgroupKeeper *keeper.Keeper + + bankKeeper *types.MockBankKeeper + accountKeeper *types.MockAccountKeeper + spKeeper *types.MockSpKeeper + paymentKeeper *types.MockPaymentKeeper + + ctx sdk.Context +} + +func (s *TestSuite) SetupTest() { + encCfg := moduletestutil.MakeTestEncodingConfig(challenge.AppModuleBasic{}) + key := storetypes.NewKVStoreKey(types.StoreKey) + testCtx := testutil.DefaultContextWithDB(s.T(), key, storetypes.NewTransientStoreKey("transient_test")) + + ctrl := gomock.NewController(s.T()) + + bankKeeper := types.NewMockBankKeeper(ctrl) + accountKeeper := types.NewMockAccountKeeper(ctrl) + spKeeper := types.NewMockSpKeeper(ctrl) + paymentKeeper := types.NewMockPaymentKeeper(ctrl) + + s.ctx = testCtx.Ctx + s.virtualgroupKeeper = keeper.NewKeeper( + encCfg.Codec, + key, + key, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + spKeeper, + accountKeeper, + bankKeeper, + paymentKeeper, + ) + + s.cdc = encCfg.Codec + s.bankKeeper = bankKeeper + s.accountKeeper = accountKeeper + s.spKeeper = spKeeper + s.paymentKeeper = paymentKeeper + + err := s.virtualgroupKeeper.SetParams(s.ctx, types.DefaultParams()) + s.Require().NoError(err) +} + +func TestTestSuite(t *testing.T) { + suite.Run(t, new(TestSuite)) +} + +func (s *TestSuite) TestSettleAndDistributeGVGFamily() { + sp := &sptypes.StorageProvider{Id: 1, FundingAddress: sample.RandAccAddress().String()} + family := &types.GlobalVirtualGroupFamily{Id: 1, VirtualPaymentAddress: sample.RandAccAddress().String()} + + s.paymentKeeper.EXPECT().QueryDynamicBalance(gomock.Any(), gomock.Any()). + Return(math.ZeroInt(), nil) + err := s.virtualgroupKeeper.SettleAndDistributeGVGFamily(s.ctx, sp, family) + require.NoError(s.T(), err) + + s.paymentKeeper.EXPECT().QueryDynamicBalance(gomock.Any(), gomock.Any()). + Return(math.ZeroInt(), errors.New("error")) + err = s.virtualgroupKeeper.SettleAndDistributeGVGFamily(s.ctx, sp, family) + require.Error(s.T(), err) + + s.paymentKeeper.EXPECT().QueryDynamicBalance(gomock.Any(), gomock.Any()). + Return(math.NewInt(1024), nil) + s.paymentKeeper.EXPECT().Withdraw(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + err = s.virtualgroupKeeper.SettleAndDistributeGVGFamily(s.ctx, sp, family) + require.NoError(s.T(), err) +} + +func (s *TestSuite) TestSettleAndDistributeGVG() { + gvg := &types.GlobalVirtualGroup{Id: 1, + VirtualPaymentAddress: sample.RandAccAddress().String(), + SecondarySpIds: []uint32{3, 6, 9}} + + s.paymentKeeper.EXPECT().QueryDynamicBalance(gomock.Any(), gomock.Any()). + Return(math.ZeroInt(), nil) + err := s.virtualgroupKeeper.SettleAndDistributeGVG(s.ctx, gvg) + require.NoError(s.T(), err) + + s.paymentKeeper.EXPECT().QueryDynamicBalance(gomock.Any(), gomock.Any()). + Return(math.ZeroInt(), errors.New("error")) + err = s.virtualgroupKeeper.SettleAndDistributeGVG(s.ctx, gvg) + require.Error(s.T(), err) + + s.paymentKeeper.EXPECT().QueryDynamicBalance(gomock.Any(), gomock.Any()). + Return(math.NewInt(1024), nil).AnyTimes() + s.paymentKeeper.EXPECT().Withdraw(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil).AnyTimes() + + sp := &sptypes.StorageProvider{Id: 1, FundingAddress: sample.RandAccAddress().String()} + s.spKeeper.EXPECT().GetStorageProvider(gomock.Any(), gomock.Any()). + Return(sp, true).AnyTimes() + err = s.virtualgroupKeeper.SettleAndDistributeGVG(s.ctx, gvg) + require.NoError(s.T(), err) +}