Skip to content

Commit

Permalink
chore: payment refactor to use global store prices for billing (#408)
Browse files Browse the repository at this point in the history
* chore: payment refactor to use global store prices for billing

* update sp module

* update e2e tests

* fix e2e tests

* add sp id in events

* fix e2e test

* include price if sp in maintenance

* refactor codes to update global price in each month

* refine sp parameters

* format code

* fix some review comments

* update dependency

* fix some ut
  • Loading branch information
forcodedancing committed Aug 15, 2023
1 parent b1e2cd5 commit 0d07730
Show file tree
Hide file tree
Showing 56 changed files with 2,180 additions and 1,577 deletions.
6 changes: 3 additions & 3 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,11 +457,8 @@ func New(
app.PaymentKeeper = *paymentmodulekeeper.NewKeeper(
appCodec,
keys[paymentmoduletypes.StoreKey],

app.BankKeeper,
app.AccountKeeper,
app.SpKeeper,

authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
paymentModule := paymentmodule.NewAppModule(appCodec, app.PaymentKeeper, app.AccountKeeper, app.BankKeeper)
Expand Down Expand Up @@ -764,6 +761,9 @@ func (app *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.R

// EndBlocker application updates every end block
func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
lastBlockTime := app.GetCheckState().Context().BlockHeader().Time.Unix()
ctx = ctx.WithValue(spmodule.LastBlockTimeKey, lastBlockTime)

resp := app.mm.EndBlock(ctx, req)
bankIavl, _ := app.CommitMultiStore().GetCommitStore(sdk.NewKVStoreKey(banktypes.StoreKey)).(*iavl.Store)
paymentIavl, _ := app.CommitMultiStore().GetCommitStore(sdk.NewKVStoreKey(paymentmoduletypes.StoreKey)).(*iavl.Store)
Expand Down
5 changes: 3 additions & 2 deletions app/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package app_test
import (
"testing"

"github.com/bnb-chain/greenfield/sdk/client/test"
"github.com/bnb-chain/greenfield/testutil"
dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"

"github.com/bnb-chain/greenfield/sdk/client/test"
"github.com/bnb-chain/greenfield/testutil"
)

func TestExportAppStateAndValidators(t *testing.T) {
Expand Down
2 changes: 2 additions & 0 deletions deployment/localup/localup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ function generate_genesis() {
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\": \"100\"/\"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/\"update_global_price_interval\": \"0\"/\"update_global_price_interval\": \"1\"/g" ${workspace}/.local/validator${i}/config/genesis.json
sed -i -e "s/\"update_price_disallowed_days\": 2/\"update_price_disallowed_days\": 0/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
done
Expand Down
252 changes: 96 additions & 156 deletions e2e/tests/payment_test.go

Large diffs are not rendered by default.

241 changes: 193 additions & 48 deletions e2e/tests/sp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ import (

type StorageProviderTestSuite struct {
core.BaseSuite
defaultParams sptypes.Params
}

func (s *StorageProviderTestSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.defaultParams = s.queryParams()
}

func (s *StorageProviderTestSuite) SetupTest() {
Expand Down Expand Up @@ -155,84 +157,145 @@ func (s *StorageProviderTestSuite) TestDeposit() {
s.Require().Equal(txRes.Code, uint32(0))
}

func (s *StorageProviderTestSuite) TestSpStoragePrice() {
func (s *StorageProviderTestSuite) TestUpdateSpStoragePrice() {
ctx := context.Background()
s.CheckSecondarySpPrice()
defer s.revertParams()

// query sp storage price by time before it exists, expect error
_, err := s.Client.QueryGlobalSpStorePriceByTime(ctx, &sptypes.QueryGlobalSpStorePriceByTimeRequest{
Timestamp: 1,
})
s.Require().Error(err)

// update params
params := s.queryParams()
params.UpdateGlobalPriceInterval = 5
s.updateParams(params)

sp := s.BaseSuite.PickStorageProvider()
spAddr := sp.OperatorKey.GetAddr().String()
spStoragePrice, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{
SpAddr: spAddr,
Timestamp: 0,
spStoragePrice, err := s.Client.QuerySpStoragePrice(ctx, &sptypes.QuerySpStoragePriceRequest{
SpAddr: spAddr,
})
s.Require().NoError(err)
s.T().Log(spStoragePrice)
// update storage price
newReadPrice := sdk.NewDec(core.RandInt64(100, 200))
newStorePrice := sdk.NewDec(core.RandInt64(10000, 20000))

// update storage price - update is ok
msgUpdateSpStoragePrice := &sptypes.MsgUpdateSpStoragePrice{
SpAddress: spAddr,
ReadPrice: newReadPrice,
StorePrice: newStorePrice,
ReadPrice: spStoragePrice.SpStoragePrice.ReadPrice,
StorePrice: spStoragePrice.SpStoragePrice.StorePrice,
FreeReadQuota: spStoragePrice.SpStoragePrice.FreeReadQuota,
}
_ = s.SendTxBlock(sp.OperatorKey, msgUpdateSpStoragePrice)
// query and assert
spStoragePrice2, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{
SpAddr: spAddr,
Timestamp: 0,
})
s.Require().NoError(err)
s.T().Log(spStoragePrice2)
// check price changed as expected
s.Require().Equal(newReadPrice, spStoragePrice2.SpStoragePrice.ReadPrice)
s.Require().Equal(newStorePrice, spStoragePrice2.SpStoragePrice.StorePrice)
s.CheckSecondarySpPrice()
// query sp storage price by time before it exists, expect error
_, err = s.Client.QueryGetSecondarySpStorePriceByTime(ctx, &sptypes.QueryGetSecondarySpStorePriceByTimeRequest{
Timestamp: 1,
})
s.Require().Error(err)
_, err = s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{
SpAddr: spAddr,
Timestamp: 1,
})
s.Require().Error(err)

time.Sleep(6 * time.Second)

// verify price is updated after interval
globalPriceResBefore, _ := s.Client.QueryGlobalSpStorePriceByTime(ctx, &sptypes.QueryGlobalSpStorePriceByTimeRequest{Timestamp: 0})
s.T().Log("globalPriceResBefore", core.YamlString(globalPriceResBefore))
priceChanged := false
globalPriceResAfter1, _ := s.Client.QueryGlobalSpStorePriceByTime(ctx, &sptypes.QueryGlobalSpStorePriceByTimeRequest{Timestamp: 0})
s.T().Log("globalPriceResAfter1", core.YamlString(globalPriceResAfter1))
for _, sp := range s.BaseSuite.StorageProviders {
msgUpdateSpStoragePrice = &sptypes.MsgUpdateSpStoragePrice{
SpAddress: sp.OperatorKey.GetAddr().String(),
ReadPrice: spStoragePrice.SpStoragePrice.ReadPrice.MulInt64(10),
StorePrice: spStoragePrice.SpStoragePrice.StorePrice.MulInt64(10),
FreeReadQuota: spStoragePrice.SpStoragePrice.FreeReadQuota,
}
s.SendTxBlock(sp.OperatorKey, msgUpdateSpStoragePrice)

globalPriceResAfter1, _ = s.Client.QueryGlobalSpStorePriceByTime(ctx, &sptypes.QueryGlobalSpStorePriceByTimeRequest{Timestamp: 0})
s.T().Log("globalPriceResAfter1", core.YamlString(globalPriceResAfter1))
if !globalPriceResAfter1.GlobalSpStorePrice.PrimaryStorePrice.Equal(globalPriceResBefore.GlobalSpStorePrice.PrimaryStorePrice) {
s.CheckGlobalSpStorePrice()
priceChanged = true
break
}
}

time.Sleep(6 * time.Second)
globalPriceResAfter2, _ := s.Client.QueryGlobalSpStorePriceByTime(ctx, &sptypes.QueryGlobalSpStorePriceByTimeRequest{Timestamp: 0})
s.T().Log("globalPriceResAfter2", core.YamlString(globalPriceResAfter2))

s.CheckGlobalSpStorePrice()
if !priceChanged { //if price not changed, then after 6 seconds, it should change
s.Require().NotEqual(globalPriceResAfter2.GlobalSpStorePrice.PrimaryStorePrice, globalPriceResBefore.GlobalSpStorePrice.PrimaryStorePrice)
s.Require().NotEqual(globalPriceResAfter2.GlobalSpStorePrice.SecondaryStorePrice, globalPriceResBefore.GlobalSpStorePrice.SecondaryStorePrice)
s.Require().NotEqual(globalPriceResAfter2.GlobalSpStorePrice.ReadPrice, globalPriceResBefore.GlobalSpStorePrice.ReadPrice)
} else { //if price not changed already, then after 6 seconds, it should not change
s.Require().Equal(globalPriceResAfter2.GlobalSpStorePrice.PrimaryStorePrice, globalPriceResAfter1.GlobalSpStorePrice.PrimaryStorePrice)
s.Require().Equal(globalPriceResAfter2.GlobalSpStorePrice.SecondaryStorePrice, globalPriceResAfter1.GlobalSpStorePrice.SecondaryStorePrice)
s.Require().Equal(globalPriceResAfter2.GlobalSpStorePrice.ReadPrice, globalPriceResAfter1.GlobalSpStorePrice.ReadPrice)
}

// update params
now := time.Now().UTC()
_, _, day := now.Date()
params = s.queryParams()
params.UpdateGlobalPriceInterval = 0 // update by month
params.UpdatePriceDisallowedDays = uint32(31 - day + 1)
s.updateParams(params)

// update storage price - third update is not ok
msgUpdateSpStoragePrice = &sptypes.MsgUpdateSpStoragePrice{
SpAddress: spAddr,
ReadPrice: spStoragePrice.SpStoragePrice.ReadPrice,
StorePrice: spStoragePrice.SpStoragePrice.StorePrice,
FreeReadQuota: spStoragePrice.SpStoragePrice.FreeReadQuota,
}
s.SendTxBlockWithExpectErrorString(msgUpdateSpStoragePrice, sp.OperatorKey, "update price is disallowed")
}

func (s *StorageProviderTestSuite) CheckSecondarySpPrice() {
func (s *StorageProviderTestSuite) CheckGlobalSpStorePrice() {
ctx := context.Background()
queryGetSecondarySpStorePriceByTimeResp, err := s.Client.QueryGetSecondarySpStorePriceByTime(ctx, &sptypes.QueryGetSecondarySpStorePriceByTimeRequest{
queryGlobalSpStorePriceByTimeResp, err := s.Client.QueryGlobalSpStorePriceByTime(ctx, &sptypes.QueryGlobalSpStorePriceByTimeRequest{
Timestamp: 0,
})
s.Require().NoError(err)
s.T().Logf("Secondary SP store price: %s", core.YamlString(queryGetSecondarySpStorePriceByTimeResp.SecondarySpStorePrice))
s.T().Logf("global SP store price: %s", core.YamlString(queryGlobalSpStorePriceByTimeResp.GlobalSpStorePrice))
// query all sps
sps, err := s.Client.StorageProviders(ctx, &sptypes.QueryStorageProvidersRequest{})
s.Require().NoError(err)
s.T().Logf("sps: %s", sps)
spNum := int64(sps.Pagination.Total)
prices := make([]sdk.Dec, 0)
storePrices := make([]sdk.Dec, 0)
readPrices := make([]sdk.Dec, 0)
for _, sp := range sps.Sps {
spStoragePrice, err := s.Client.QueryGetSpStoragePriceByTime(ctx, &sptypes.QueryGetSpStoragePriceByTimeRequest{
SpAddr: sp.OperatorAddress,
Timestamp: 0,
})
s.Require().NoError(err)
s.T().Logf("sp: %s, storage price: %s", sp.OperatorAddress, core.YamlString(spStoragePrice.SpStoragePrice))
prices = append(prices, spStoragePrice.SpStoragePrice.StorePrice)
if sp.Status == sptypes.STATUS_IN_SERVICE || sp.Status == sptypes.STATUS_IN_MAINTENANCE {
spStoragePrice, err := s.Client.QuerySpStoragePrice(ctx, &sptypes.QuerySpStoragePriceRequest{
SpAddr: sp.OperatorAddress,
})
s.Require().NoError(err)
s.T().Logf("sp: %s, storage price: %s", sp.OperatorAddress, core.YamlString(spStoragePrice.SpStoragePrice))
storePrices = append(storePrices, spStoragePrice.SpStoragePrice.StorePrice)
readPrices = append(readPrices, spStoragePrice.SpStoragePrice.ReadPrice)
}
}
sort.Slice(prices, func(i, j int) bool { return prices[i].LT(prices[j]) })
var median sdk.Dec

sort.Slice(storePrices, func(i, j int) bool { return storePrices[i].LT(storePrices[j]) })
var storeMedian sdk.Dec
if spNum%2 == 0 {
median = prices[spNum/2-1].Add(prices[spNum/2]).QuoInt64(2)
storeMedian = storePrices[spNum/2-1].Add(storePrices[spNum/2]).QuoInt64(2)
} else {
median = prices[spNum/2]
storeMedian = storePrices[spNum/2]
}

sort.Slice(readPrices, func(i, j int) bool { return readPrices[i].LT(readPrices[j]) })
var readMedian sdk.Dec
if spNum%2 == 0 {
readMedian = readPrices[spNum/2-1].Add(readPrices[spNum/2]).QuoInt64(2)
} else {
readMedian = readPrices[spNum/2]
}

s.Require().Equal(storeMedian, queryGlobalSpStorePriceByTimeResp.GlobalSpStorePrice.PrimaryStorePrice)
params, err := s.Client.SpQueryClient.Params(ctx, &sptypes.QueryParamsRequest{})
s.Require().NoError(err)
expectedSecondarySpStorePrice := params.Params.SecondarySpStorePriceRatio.Mul(median)
s.Require().Equal(expectedSecondarySpStorePrice, queryGetSecondarySpStorePriceByTimeResp.SecondarySpStorePrice.StorePrice)
expectedSecondarySpStorePrice := params.Params.SecondarySpStorePriceRatio.Mul(storeMedian)
s.Require().Equal(expectedSecondarySpStorePrice, queryGlobalSpStorePriceByTimeResp.GlobalSpStorePrice.SecondaryStorePrice)
s.Require().Equal(readMedian, queryGlobalSpStorePriceByTimeResp.GlobalSpStorePrice.ReadPrice)
}

func TestStorageProviderTestSuite(t *testing.T) {
Expand Down Expand Up @@ -377,3 +440,85 @@ func (s *StorageProviderTestSuite) TestUpdateStorageProviderStatus() {
s.Require().NoError(err)
s.Require().Equal(sptypes.STATUS_IN_SERVICE, spResp.StorageProvider.Status)
}

func (s *StorageProviderTestSuite) queryParams() sptypes.Params {
queryParamsRequest := sptypes.QueryParamsRequest{}
queryParamsResponse, err := s.Client.SpQueryClient.Params(context.Background(), &queryParamsRequest)
s.Require().NoError(err)
s.T().Log("params", core.YamlString(queryParamsResponse.Params))
return queryParamsResponse.Params
}

func (s *StorageProviderTestSuite) revertParams() {
s.updateParams(s.defaultParams)
}

func (s *StorageProviderTestSuite) updateParams(params sptypes.Params) {
var err error
validator := s.Validator.GetAddr()

ctx := context.Background()

queryParamsRequest := &sptypes.QueryParamsRequest{}
queryParamsResponse, err := s.Client.SpQueryClient.Params(ctx, queryParamsRequest)
s.Require().NoError(err)
s.T().Log("params before", core.YamlString(queryParamsResponse.Params))

msgUpdateParams := &sptypes.MsgUpdateParams{
Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(),
Params: params,
}

msgProposal, err := v1.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 := &v1.QueryProposalRequest{ProposalId: proposalId}
_, err = s.Client.GovQueryClientV1.Proposal(ctx, queryProposal)
s.Require().NoError(err)

// 4. submit MsgVote and wait the proposal exec
msgVote := v1.NewMsgVote(validator, proposalId, v1.OptionYes, "test")
txRes = s.SendTxBlock(s.Validator, msgVote)
s.Require().Equal(txRes.Code, uint32(0))

queryVoteParamsReq := v1.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, v1.ProposalStatus_PROPOSAL_STATUS_PASSED)

queryParamsRequest = &sptypes.QueryParamsRequest{}
queryParamsResponse, err = s.Client.SpQueryClient.Params(ctx, queryParamsRequest)
s.Require().NoError(err)
s.T().Log("params after", core.YamlString(queryParamsResponse.Params))
}
Loading

0 comments on commit 0d07730

Please sign in to comment.