Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into add-metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
okdas committed Oct 30, 2024
2 parents 5f693e5 + ff76430 commit 6cac26c
Show file tree
Hide file tree
Showing 11 changed files with 1,765 additions and 183 deletions.
953 changes: 909 additions & 44 deletions api/poktroll/tokenomics/event.pulsar.go

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions e2e/tests/0_settlement.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# that can be used to clear the state of the chain between tests.

Feature: Tokenomics Namespace
Scenario: Settle the session when a valid claim is within max limits and a valid proof is submitted and required via threshold
Scenario: TLM Mint=Burn when a valid claim is within max limits and a valid proof is submitted and required via threshold
# Baseline
Given the user has the pocketd binary installed
# Network preparation and validation
Expand Down Expand Up @@ -35,12 +35,13 @@ Feature: Tokenomics Namespace
And the user should wait for the ClaimSettled event with "THRESHOLD" proof requirement to be broadcast
# Validate the results
# Please note that supplier mint is > app burn because of inflation
# TODO_TECHDEBT: Update this test such the the inflation is set and enforce that Mint=Burn
# TODO_TECHDEBT: Update this test such the inflation is set and enforce that Mint=Burn
# Then add a separate test that only validates that inflation is enforced correctly
Then the account balance of "supplier1" should be "898" uPOKT "more" than before
And the "application" stake of "app1" should be "840" uPOKT "less" than before
# The application stake should be less 840 * (1 + glbal_inflation) = 840 * 1.1 = 924
And the "application" stake of "app1" should be "924" uPOKT "less" than before

Scenario: Settle the session when a valid claim is create but not required
Scenario: TLM Mint=Burn when a valid claim is create but not required
# Baseline
Given the user has the pocketd binary installed
# Network preparation and validation
Expand Down Expand Up @@ -70,9 +71,10 @@ Feature: Tokenomics Namespace
And the user should wait for the ClaimSettled event with "NOT_REQUIRED" proof requirement to be broadcast
# Validate the results
# Please note that supplier mint is > app burn because of inflation
# TODO_TECHDEBT: Update this test such the the inflation is set and enforce that Mint=Burn
# TODO_TECHDEBT: Update this test such the inflation is set and enforce that Mint=Burn
Then the account balance of "supplier1" should be "449" uPOKT "more" than before
And the "application" stake of "app1" should be "420" uPOKT "less" than before
# The application stake should be less 420 * (1 + glbal_inflation) = 420 * 1.1 = 462
And the "application" stake of "app1" should be "462" uPOKT "less" than before

# TODO_TEST: Implement the following scenarios
# Scenario: Supplier revenue shares are properly distributed
Expand Down
11 changes: 11 additions & 0 deletions proto/poktroll/tokenomics/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,15 @@ message EventSupplierSlashed {
// Amount slashed from the supplier's stake due to the expired claims.
// This is a function of the number of expired claims and proof missing penalty.
cosmos.base.v1beta1.Coin slashing_amount = 3;
}

// EventApplicationReimbursementRequest is emitted when an application requests
// a reimbursement.
message EventApplicationReimbursementRequest {
string application_addr = 1;
string supplier_operator_addr = 2;
string supplier_owner_addr = 3;
string service_id = 4;
string session_id = 5;
cosmos.base.v1beta1.Coin amount = 6;
}
14 changes: 9 additions & 5 deletions tests/integration/application/min_stake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"testing"

cosmoslog "cosmossdk.io/log"
"cosmossdk.io/math"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/stretchr/testify/require"
Expand All @@ -25,6 +24,7 @@ import (
sessiontypes "github.com/pokt-network/poktroll/x/session/types"
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
suppliertypes "github.com/pokt-network/poktroll/x/supplier/types"
tokenomicskeeper "github.com/pokt-network/poktroll/x/tokenomics/keeper"
)

type applicationMinStakeTestSuite struct {
Expand All @@ -51,7 +51,7 @@ func TestApplicationMinStakeTestSuite(t *testing.T) {
}

func (s *applicationMinStakeTestSuite) SetupTest() {
s.keepers, s.ctx = keeper.NewTokenomicsModuleKeepers(s.T(), cosmoslog.NewNopLogger())
s.keepers, s.ctx = keeper.NewTokenomicsModuleKeepers(s.T(), cosmoslog.NewNopLogger(), keeper.WithProofRequirement(false))

proofParams := prooftypes.DefaultParams()
proofParams.ProofRequestProbability = 0
Expand Down Expand Up @@ -235,7 +235,8 @@ func (s *applicationMinStakeTestSuite) getExpectedApp(claim *prooftypes.Claim) *
expectedBurnCoin, err := claim.GetClaimeduPOKT(sharedParams, relayMiningDifficulty)
require.NoError(s.T(), err)

expectedEndStake := s.appStake.Sub(expectedBurnCoin)
globalInflationAmt, _ := tokenomicskeeper.CalculateGlobalPerClaimMintInflationFromSettlementAmount(expectedBurnCoin)
expectedEndStake := s.appStake.Sub(expectedBurnCoin).Sub(globalInflationAmt)
return &apptypes.Application{
Address: s.appBech32,
Stake: &expectedEndStake,
Expand Down Expand Up @@ -304,8 +305,11 @@ func (s *applicationMinStakeTestSuite) assertUnbondingEndEventObserved(expectedA
func (s *applicationMinStakeTestSuite) assertAppStakeIsReturnedToBalance() {
s.T().Helper()

expectedAppBurn := math.NewInt(int64(s.numRelays * s.numComputeUnitsPerRelay * sharedtypes.DefaultComputeUnitsToTokensMultiplier))
expectedAppBalance := s.appStake.SubAmount(expectedAppBurn)
expectedAppBurn := int64(s.numRelays * s.numComputeUnitsPerRelay * sharedtypes.DefaultComputeUnitsToTokensMultiplier)
expectedAppBurnCoin := cosmostypes.NewInt64Coin(volatile.DenomuPOKT, expectedAppBurn)
globalInflationCoin, _ := tokenomicskeeper.CalculateGlobalPerClaimMintInflationFromSettlementAmount(expectedAppBurnCoin)
expectedAppBalance := s.appStake.Sub(expectedAppBurnCoin).Sub(globalInflationCoin)

appBalance := s.getAppBalance()
require.Equal(s.T(), expectedAppBalance.Amount.Int64(), appBalance.Amount.Int64())
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package integration_test

import (
"math"
"math/big"
"testing"

Expand Down Expand Up @@ -84,6 +83,7 @@ func TestComputeNewDifficultyHash_RewardsReflectWorkCompleted(t *testing.T) {
testutils.WithService(service),
testutils.WithApplication(application),
testutils.WithSupplier(supplier),
testutils.WithProofRequirement(false),
)
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx = sdkCtx.WithBlockHeight(1)
Expand All @@ -94,13 +94,6 @@ func TestComputeNewDifficultyHash_RewardsReflectWorkCompleted(t *testing.T) {
err := keepers.SharedKeeper.SetParams(sdkCtx, sharedParams)
require.NoError(t, err)

// Set the global proof params so we never need a proof (for simplicity of this test)
err = keepers.ProofKeeper.SetParams(sdkCtx, prooftypes.Params{
ProofRequestProbability: 0, // we never need a proof randomly
ProofRequirementThreshold: &sdk.Coin{Denom: volatile.DenomuPOKT, Amount: sdkmath.NewInt(math.MaxInt64)}, // a VERY high threshold
})
require.NoError(t, err)

// Update the relay mining difficulty so there's always a difficulty to retrieve
// for the test service.
_, err = keepers.ServiceKeeper.UpdateRelayMiningDifficulty(sdkCtx, map[string]uint64{service.Id: 1})
Expand Down
45 changes: 40 additions & 5 deletions testutil/keeper/tokenomics.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package keeper

import (
"context"
"math"
"testing"

"cosmossdk.io/log"
"cosmossdk.io/math"
cosmosmath "cosmossdk.io/math"
"cosmossdk.io/store"
"cosmossdk.io/store/metrics"
storetypes "cosmossdk.io/store/types"
Expand All @@ -29,6 +30,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/app"
"github.com/pokt-network/poktroll/app/volatile"
"github.com/pokt-network/poktroll/testutil/sample"
"github.com/pokt-network/poktroll/testutil/tokenomics/mocks"
appkeeper "github.com/pokt-network/poktroll/x/application/keeper"
Expand Down Expand Up @@ -117,7 +119,7 @@ func TokenomicsKeeperWithActorAddrs(t testing.TB) (
// Prepare the test application.
application := apptypes.Application{
Address: sample.AccAddress(),
Stake: &sdk.Coin{Denom: "upokt", Amount: math.NewInt(100000)},
Stake: &sdk.Coin{Denom: "upokt", Amount: cosmosmath.NewInt(100000)},
ServiceConfigs: []*sharedtypes.ApplicationServiceConfig{{ServiceId: service.Id}},
}

Expand All @@ -126,7 +128,7 @@ func TokenomicsKeeperWithActorAddrs(t testing.TB) (
supplier := sharedtypes.Supplier{
OwnerAddress: supplierOwnerAddr,
OperatorAddress: supplierOwnerAddr,
Stake: &sdk.Coin{Denom: "upokt", Amount: math.NewInt(100000)},
Stake: &sdk.Coin{Denom: "upokt", Amount: cosmosmath.NewInt(100000)},
Services: []*sharedtypes.SupplierServiceConfig{
{
ServiceId: service.Id,
Expand Down Expand Up @@ -199,6 +201,9 @@ func TokenomicsKeeperWithActorAddrs(t testing.TB) (
mockBankKeeper.EXPECT().
SendCoinsFromModuleToModule(gomock.Any(), tokenomicstypes.ModuleName, suppliertypes.ModuleName, gomock.Any()).
AnyTimes()
mockBankKeeper.EXPECT().
SendCoinsFromModuleToModule(gomock.Any(), apptypes.ModuleName, tokenomicstypes.ModuleName, gomock.Any()).
AnyTimes()

// Mock the account keeper
mockAccountKeeper := mocks.NewMockAccountKeeper(ctrl)
Expand Down Expand Up @@ -346,9 +351,9 @@ func NewTokenomicsModuleKeepers(
require.NoError(t, bankKeeper.SetParams(sdkCtx, banktypes.DefaultParams()))

// Provide some initial funds to the suppliers & applications module accounts.
err = bankKeeper.MintCoins(sdkCtx, suppliertypes.ModuleName, sdk.NewCoins(sdk.NewCoin("upokt", math.NewInt(1000000000000))))
err = bankKeeper.MintCoins(sdkCtx, suppliertypes.ModuleName, sdk.NewCoins(sdk.NewCoin("upokt", cosmosmath.NewInt(1000000000000))))
require.NoError(t, err)
err = bankKeeper.MintCoins(sdkCtx, apptypes.ModuleName, sdk.NewCoins(sdk.NewCoin("upokt", math.NewInt(1000000000000))))
err = bankKeeper.MintCoins(sdkCtx, apptypes.ModuleName, sdk.NewCoins(sdk.NewCoin("upokt", cosmosmath.NewInt(1000000000000))))
require.NoError(t, err)

// Construct a real shared keeper.
Expand Down Expand Up @@ -513,3 +518,33 @@ func WithProposerAddr(addr string) TokenomicsModuleKeepersOpt {
return sdkCtx
}
}

// WithProofRequirement is an option to enable or disable the proof requirement
// in the tokenomics module keepers by setting the proof request probability to
// 1 or 0, respectively whie setting the proof requirement threshold to 0 or
// MaxInt64, respectively.
func WithProofRequirement(proofRequired bool) TokenomicsModuleKeepersOpt {
return func(ctx context.Context, keepers *TokenomicsModuleKeepers) context.Context {

proofParams := keepers.ProofKeeper.GetParams(ctx)
if proofRequired {
// Require a proof 100% of the time probabilistically speaking.
proofParams.ProofRequestProbability = 1
// Require a proof of any claim amount (i.e. anything greater than 0).
proofRequirementThreshold := cosmostypes.NewInt64Coin(volatile.DenomuPOKT, 0)
proofParams.ProofRequirementThreshold = &proofRequirementThreshold
} else {
// Never require a proof probabilistically speaking.
proofParams.ProofRequestProbability = 0
// Require a proof for MaxInt64 claim amount (i.e. should never trigger).
proofRequirementThreshold := cosmostypes.NewInt64Coin(volatile.DenomuPOKT, math.MaxInt64)
proofParams.ProofRequirementThreshold = &proofRequirementThreshold
}

if err := keepers.ProofKeeper.SetParams(ctx, proofParams); err != nil {
panic(err)
}

return ctx
}
}
47 changes: 46 additions & 1 deletion x/tokenomics/keeper/settle_pending_claims.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"context"
"fmt"

"cosmossdk.io/math"
Expand All @@ -9,6 +10,7 @@ import (

"github.com/pokt-network/poktroll/app/volatile"
"github.com/pokt-network/poktroll/telemetry"
apptypes "github.com/pokt-network/poktroll/x/application/types"
prooftypes "github.com/pokt-network/poktroll/x/proof/types"
servicekeeper "github.com/pokt-network/poktroll/x/service/keeper"
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
Expand Down Expand Up @@ -36,6 +38,15 @@ func (k Keeper) SettlePendingClaims(ctx sdk.Context) (
return settledResult, expiredResult, err
}

// Capture the applications initial stake which will be used to calculate the
// max share any claim could burn from the application stake.
// This ensures that each supplier can calculate the maximum amount it can take
// from an application's stake.
applicationInitialStakeMap, err := k.getApplicationInitialStakeMap(ctx, expiringClaims)
if err != nil {
return settledResult, expiredResult, err
}

blockHeight := ctx.BlockHeight()

logger.Info(fmt.Sprintf("found %d expiring claims at block height %d", len(expiringClaims), blockHeight))
Expand Down Expand Up @@ -189,8 +200,11 @@ func (k Keeper) SettlePendingClaims(ctx sdk.Context) (
// 1. The claim does not require a proof.
// 2. The claim requires a proof and a valid proof was found.

appAddress := claim.GetSessionHeader().GetApplicationAddress()
applicationInitialStake := applicationInitialStakeMap[appAddress]

// Manage the mint & burn accounting for the claim.
if err = k.ProcessTokenLogicModules(ctx, &claim); err != nil {
if err = k.ProcessTokenLogicModules(ctx, &claim, applicationInitialStake); err != nil {
logger.Error(fmt.Sprintf("error processing token logic modules for claim %q: %v", claim.SessionHeader.SessionId, err))
return settledResult, expiredResult, err
}
Expand Down Expand Up @@ -432,6 +446,37 @@ func (k Keeper) slashSupplierStake(
return nil
}

// getApplicationInitialStakeMap returns a map from an application address to the
// initial stake of the application. This is used to calculate the maximum share
// any claim could burn from the application stake.
func (k Keeper) getApplicationInitialStakeMap(
ctx context.Context,
expiringClaims []prooftypes.Claim,
) (applicationInitialStakeMap map[string]sdk.Coin, err error) {
applicationInitialStakeMap = make(map[string]sdk.Coin)
for _, claim := range expiringClaims {
appAddress := claim.SessionHeader.ApplicationAddress
// The same application is participating in other claims being settled,
// so we already capture its initial stake.
if _, isAppFound := applicationInitialStakeMap[appAddress]; isAppFound {
continue
}

app, isAppFound := k.applicationKeeper.GetApplication(ctx, appAddress)
if !isAppFound {
err := apptypes.ErrAppNotFound.Wrapf(
"trying to settle a claim for an application that does not exist (which should never happen) with address: %q",
appAddress,
)
return nil, err
}

applicationInitialStakeMap[appAddress] = *app.GetStake()
}

return applicationInitialStakeMap, nil
}

// finalizeTelemetry logs telemetry metrics for a claim based on its stage (e.g., EXPIRED, SETTLED).
// Meant to run deferred.
func (k Keeper) finalizeTelemetry(
Expand Down
Loading

0 comments on commit 6cac26c

Please sign in to comment.