Skip to content

Commit

Permalink
[Testing] refactor: tokenomics keeper tests (#603)
Browse files Browse the repository at this point in the history
  • Loading branch information
bryanchriswhite authored Jun 14, 2024
1 parent f85081b commit d586eaa
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 50 deletions.
5 changes: 5 additions & 0 deletions testutil/keeper/tokenomics.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type TokenomicsModuleKeepers struct {
tokenomicstypes.BankKeeper
tokenomicstypes.ApplicationKeeper
tokenomicstypes.ProofKeeper
tokenomicstypes.SharedKeeper

Codec *codec.ProtoCodec
}
Expand Down Expand Up @@ -187,6 +188,7 @@ func NewTokenomicsModuleKeepers(
apptypes.StoreKey,
suppliertypes.StoreKey,
prooftypes.StoreKey,
sharedtypes.StoreKey,
)

// Construct a multistore & mount store keys for each keeper that will interact with the state store.
Expand Down Expand Up @@ -247,6 +249,7 @@ func NewTokenomicsModuleKeepers(
logger,
authority.String(),
)
require.NoError(t, sharedKeeper.SetParams(ctx, sharedtypes.DefaultParams()))

// Construct gateway keeper with a mocked bank keeper.
gatewayKeeper := gatewaykeeper.NewKeeper(
Expand Down Expand Up @@ -306,6 +309,7 @@ func NewTokenomicsModuleKeepers(
accountKeeper,
sharedKeeper,
)
require.NoError(t, proofKeeper.SetParams(ctx, prooftypes.DefaultParams()))

// Construct a real tokenomics keeper so that claims & tokenomics can be created.
tokenomicsKeeper := tokenomicskeeper.NewKeeper(
Expand All @@ -327,6 +331,7 @@ func NewTokenomicsModuleKeepers(
BankKeeper: &bankKeeper,
ApplicationKeeper: &appKeeper,
ProofKeeper: &proofKeeper,
SharedKeeper: &sharedKeeper,

Codec: cdc,
}
Expand Down
127 changes: 87 additions & 40 deletions x/tokenomics/keeper/keeper_settle_pending_claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"cosmossdk.io/math"
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cosmos/cosmos-sdk/types"
sdk "github.com/cosmos/cosmos-sdk/types"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
Expand All @@ -21,8 +21,8 @@ import (
apptypes "github.com/pokt-network/poktroll/x/application/types"
prooftypes "github.com/pokt-network/poktroll/x/proof/types"
sessiontypes "github.com/pokt-network/poktroll/x/session/types"
"github.com/pokt-network/poktroll/x/shared"
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
tokenomicskeeper "github.com/pokt-network/poktroll/x/tokenomics/keeper"
tokenomicstypes "github.com/pokt-network/poktroll/x/tokenomics/types"
)

Expand All @@ -39,19 +39,31 @@ func init() {
type TestSuite struct {
suite.Suite

sdkCtx sdk.Context
sdkCtx cosmostypes.Context
ctx context.Context
keepers keepertest.TokenomicsModuleKeepers
claim prooftypes.Claim
proof prooftypes.Proof

expectedComputeUnits uint64
}

// SetupTest creates the following and stores them in the suite:
// - An cosmostypes.Context.
// - A keepertest.TokenomicsModuleKeepers to provide access to integrated keepers.
// - An expectedComputeUnits which is the default proof_requirement_threshold.
// - A claim that will require a proof via threshold, given the default proof params.
// - A proof which contains only the session header supplier address.
func (s *TestSuite) SetupTest() {
supplierAddr := sample.AccAddress()
appAddr := sample.AccAddress()

s.keepers, s.ctx = keepertest.NewTokenomicsModuleKeepers(s.T())
s.sdkCtx = sdk.UnwrapSDKContext(s.ctx)
s.sdkCtx = cosmostypes.UnwrapSDKContext(s.ctx)

// Set the suite expectedComputeUnits to equal the default proof_requirement_threshold
// such that by default, s.claim will require a proof 100% of the time.
s.expectedComputeUnits = prooftypes.DefaultProofRequirementThreshold

// Prepare a claim that can be inserted
s.claim = prooftypes.Claim{
Expand All @@ -63,7 +75,10 @@ func (s *TestSuite) SetupTest() {
SessionStartBlockHeight: 1,
SessionEndBlockHeight: testsession.GetSessionEndHeightWithDefaultParams(1),
},
RootHash: testutilproof.SmstRootWithSum(69),

// Set the suite expectedComputeUnits to be equal to the default threshold.
// This SHOULD make the claim require a proof given the default proof parameters.
RootHash: testutilproof.SmstRootWithSum(s.expectedComputeUnits),
}

// Prepare a claim that can be inserted
Expand All @@ -81,6 +96,9 @@ func (s *TestSuite) SetupTest() {
s.keepers.SetApplication(s.ctx, app)
}

// TestSettleExpiringClaimsSuite tests the claim settlement process.
// NB: Each test scenario (method) is run in isolation and #TestSetup() is called
// for each prior to running.
func TestSettlePendingClaims(t *testing.T) {
suite.Run(t, new(TestSuite))
}
Expand All @@ -89,7 +107,8 @@ func (s *TestSuite) TestSettlePendingClaims_ClaimPendingBeforeSettlement() {
// Retrieve default values
t := s.T()
ctx := s.ctx
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx := cosmostypes.UnwrapSDKContext(ctx)
sharedParams := s.keepers.SharedKeeper.GetParams(ctx)

// 0. Add the claim & verify it exists
claim := s.claim
Expand All @@ -102,19 +121,29 @@ func (s *TestSuite) TestSettlePendingClaims_ClaimPendingBeforeSettlement() {
blockHeight := claim.SessionHeader.SessionEndBlockHeight - 2 // session is still active
sdkCtx = sdkCtx.WithBlockHeight(blockHeight)
numClaimsSettled, numClaimsExpired, _, err := s.keepers.SettlePendingClaims(sdkCtx)
// Check that no claims were settled
require.NoError(t, err)

// Check that no claims were settled.
require.Equal(t, uint64(0), numClaimsSettled)

// Validate that no claims expired.
require.Equal(t, uint64(0), numClaimsExpired)
// Validate that the claim still exists

// Validate that one claim still remains.
claims = s.keepers.GetAllClaims(ctx)
require.Len(t, claims, 1)

// Calculate a block height which is within the proof window.
proofWindowOpenHeight := shared.GetProofWindowOpenHeight(
&sharedParams, claim.SessionHeader.SessionEndBlockHeight,
)
proofWindowCloseHeight := shared.GetProofWindowCloseHeight(
&sharedParams, claim.SessionHeader.SessionEndBlockHeight,
)
blockHeight = (proofWindowCloseHeight - proofWindowOpenHeight) / 2

// 2. Settle pending claims just after the session ended.
// Expectations: Claims should not be settled because the proof window hasn't closed yet.
// TODO_BLOCKER(@red-0ne): Use the governance parameters for more
// precise block heights once they are implemented.
blockHeight = claim.SessionHeader.SessionEndBlockHeight + 2 // session ended but proof window is still open
sdkCtx = sdkCtx.WithBlockHeight(blockHeight)
numClaimsSettled, numClaimsExpired, _, err = s.keepers.SettlePendingClaims(sdkCtx)
// Check that no claims were settled
Expand All @@ -126,16 +155,15 @@ func (s *TestSuite) TestSettlePendingClaims_ClaimPendingBeforeSettlement() {
require.Len(t, claims, 1)
}

func (s *TestSuite) TestSettlePendingClaims_ClaimExpired_ProofRequiredAndNotProvided() {
func (s *TestSuite) TestSettlePendingClaims_ClaimExpired_ProofRequiredAndNotProvided_ViaThreshold() {
// Retrieve default values
t := s.T()
ctx := s.ctx
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx := cosmostypes.UnwrapSDKContext(ctx)
sharedParams := s.keepers.SharedKeeper.GetParams(ctx)

// Create a claim that requires a proof
claim := s.claim
numComputeUnits := uint64(tokenomicskeeper.ProofRequiredComputeUnits + 1)
claim.RootHash = testutilproof.SmstRootWithSum(numComputeUnits)

// 0. Add the claim & verify it exists
s.keepers.UpsertClaim(ctx, claim)
Expand All @@ -144,15 +172,19 @@ func (s *TestSuite) TestSettlePendingClaims_ClaimExpired_ProofRequiredAndNotProv

// 1. Settle pending claims after proof window closes
// Expectation: All (1) claims should be expired.
// TODO_BLOCKER(@red-0ne): Use the governance parameters for more precise block heights once they are implemented.
blockHeight := claim.SessionHeader.SessionEndBlockHeight * 10 // proof window has definitely closed at this point
// NB: proofs should be rejected when the current height equals the proof window close height.
blockHeight := shared.GetProofWindowCloseHeight(&sharedParams, claim.SessionHeader.SessionEndBlockHeight)
sdkCtx = sdkCtx.WithBlockHeight(blockHeight)
numClaimsSettled, numClaimsExpired, _, err := s.keepers.SettlePendingClaims(sdkCtx)
// Check that no claims were settled
require.NoError(t, err)

// Check that no claims were settled.
require.Equal(t, uint64(0), numClaimsSettled)

// Validate that one claims expired
require.Equal(t, uint64(1), numClaimsExpired)
// Validate that the claims expired

// Validate that no claims remain.
claims = s.keepers.GetAllClaims(ctx)
require.Len(t, claims, 0)

Expand All @@ -162,19 +194,18 @@ func (s *TestSuite) TestSettlePendingClaims_ClaimExpired_ProofRequiredAndNotProv
// Validate the expiration event
expectedEvent, ok := s.getClaimEvent(events, "poktroll.tokenomics.EventClaimExpired").(*tokenomicstypes.EventClaimExpired)
require.True(t, ok)
require.Equal(t, numComputeUnits, expectedEvent.ComputeUnits)
require.Equal(t, s.expectedComputeUnits, expectedEvent.ComputeUnits)
}

func (s *TestSuite) TestSettlePendingClaims_ClaimSettled_ProofRequiredAndProvided() {
func (s *TestSuite) TestSettlePendingClaims_ClaimSettled_ProofRequiredAndProvided_ViaThreshold() {
// Retrieve default values
t := s.T()
ctx := s.ctx
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx := cosmostypes.UnwrapSDKContext(ctx)
sharedParams := s.keepers.SharedKeeper.GetParams(ctx)

// Create a claim that requires a proof
claim := s.claim
numComputeUnits := uint64(tokenomicskeeper.ProofRequiredComputeUnits + 1)
claim.RootHash = testutilproof.SmstRootWithSum(numComputeUnits)

// 0. Add the claim & verify it exists
s.keepers.UpsertClaim(ctx, claim)
Expand All @@ -186,15 +217,19 @@ func (s *TestSuite) TestSettlePendingClaims_ClaimSettled_ProofRequiredAndProvide

// 1. Settle pending claims after proof window closes
// Expectation: All (1) claims should be claimed.
// TODO_BLOCKER(@red-0ne): Use the governance parameters for more precise block heights once they are implemented.
blockHeight := s.claim.SessionHeader.SessionEndBlockHeight * 10 // proof window has definitely closed at this point
// NB: proofs should be rejected when the current height equals the proof window close height.
blockHeight := shared.GetProofWindowCloseHeight(&sharedParams, claim.SessionHeader.SessionEndBlockHeight)
sdkCtx = sdkCtx.WithBlockHeight(blockHeight)
numClaimsSettled, numClaimsExpired, _, err := s.keepers.SettlePendingClaims(sdkCtx)
// Check that no claims were settled
require.NoError(t, err)

// Check that one claim was settled.
require.Equal(t, uint64(1), numClaimsSettled)

// Validate that no claims expired.
require.Equal(t, uint64(0), numClaimsExpired)
// Validate that the claims expired

// Validate that no claims remain.
claims = s.keepers.GetAllClaims(ctx)
require.Len(t, claims, 0)

Expand All @@ -203,20 +238,28 @@ func (s *TestSuite) TestSettlePendingClaims_ClaimSettled_ProofRequiredAndProvide
expectedEvent, ok := s.getClaimEvent(events, "poktroll.tokenomics.EventClaimSettled").(*tokenomicstypes.EventClaimSettled)
require.True(t, ok)
require.True(t, expectedEvent.ProofRequired)
require.Equal(t, numComputeUnits, expectedEvent.ComputeUnits)

require.Equal(t, s.expectedComputeUnits, expectedEvent.ComputeUnits)
}

func (s *TestSuite) TestSettlePendingClaims_Settles_WhenAProofIsNotRequired() {
// Retrieve default values
t := s.T()
ctx := s.ctx
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx := cosmostypes.UnwrapSDKContext(ctx)
sharedParams := s.keepers.SharedKeeper.GetParams(ctx)

// Create a claim that does not require a proof
claim := s.claim
numComputeUnits := uint64(tokenomicskeeper.ProofRequiredComputeUnits - 1)
claim.RootHash = testutilproof.SmstRootWithSum(numComputeUnits)

// Set the proof parameters such that s.claim DOES NOT require a proof because
// the proof_request_probability is 0% AND because the proof_requirement_threshold
// exceeds s.expectedComputeUnits, which matches s.claim.
err := s.keepers.ProofKeeper.SetParams(ctx, prooftypes.Params{
ProofRequestProbability: 0,
// +1 to push the threshold above s.claim's compute units
ProofRequirementThreshold: s.expectedComputeUnits + 1,
})
require.NoError(t, err)

// 0. Add the claim & verify it exists
s.keepers.UpsertClaim(ctx, claim)
Expand All @@ -225,15 +268,19 @@ func (s *TestSuite) TestSettlePendingClaims_Settles_WhenAProofIsNotRequired() {

// 1. Settle pending claims after proof window closes
// Expectation: All (1) claims should be claimed.
// TODO_BLOCKER(@red-0ne): Use the governance parameters for more precise block heights once they are implemented.
blockHeight := claim.SessionHeader.SessionEndBlockHeight * 10 // proof window has definitely closed at this point
// NB: proofs should be rejected when the current height equals the proof window close height.
blockHeight := shared.GetProofWindowCloseHeight(&sharedParams, claim.SessionHeader.SessionEndBlockHeight)
sdkCtx = sdkCtx.WithBlockHeight(blockHeight)
numClaimsSettled, numClaimsExpired, _, err := s.keepers.SettlePendingClaims(sdkCtx)
// Check that no claims were settled
require.NoError(t, err)

// Check that one claim was settled.
require.Equal(t, uint64(1), numClaimsSettled)

// Validate that no claims expired.
require.Equal(t, uint64(0), numClaimsExpired)
// Validate that the claims expired

// Validate that no claims remain.
claims = s.keepers.GetAllClaims(ctx)
require.Len(t, claims, 0)

Expand All @@ -242,7 +289,7 @@ func (s *TestSuite) TestSettlePendingClaims_Settles_WhenAProofIsNotRequired() {
expectedEvent, ok := s.getClaimEvent(events, "poktroll.tokenomics.EventClaimSettled").(*tokenomicstypes.EventClaimSettled)
require.True(t, ok)
require.False(t, expectedEvent.ProofRequired)
require.Equal(t, numComputeUnits, expectedEvent.ComputeUnits)
require.Equal(t, s.expectedComputeUnits, expectedEvent.ComputeUnits)
}

func (s *TestSuite) TestSettlePendingClaims_DoesNotSettle_BeforeProofWindowCloses() {
Expand All @@ -264,14 +311,14 @@ func (s *TestSuite) TestSettlePendingClaims_MultipleClaimsSettle_WithMultipleApp
// getClaimEvent verifies that there is exactly one event of type protoType in
// the given events and returns it. If there are 0 or more than 1 events of the
// given type, it fails the test.
func (s *TestSuite) getClaimEvent(events sdk.Events, protoType string) proto.Message {
func (s *TestSuite) getClaimEvent(events cosmostypes.Events, protoType string) proto.Message {
var parsedEvent proto.Message
numExpectedEvents := 0
for _, event := range events {
switch event.Type {
case protoType:
var err error
parsedEvent, err = sdk.ParseTypedEvent(abci.Event(event))
parsedEvent, err = cosmostypes.ParseTypedEvent(abci.Event(event))
s.Require().NoError(err)
numExpectedEvents++
default:
Expand Down
13 changes: 3 additions & 10 deletions x/tokenomics/keeper/settle_pending_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@ import (
"github.com/pokt-network/poktroll/x/tokenomics/types"
)

const (
// TODO_BLOCKER(@bryanchriswhite): Implement this properly. Using a constant
// for "probabilistic proofs" is just a simple placeholder mechanism to get
// #359 over the finish line.
ProofRequiredComputeUnits = 100
)

// SettlePendingClaims settles all pending (i.e. expiring) claims.
// If a claim is expired and requires a proof and a proof IS available -> it's settled.
// If a claim is expired and requires a proof and a proof IS NOT available -> it's deleted.
Expand Down Expand Up @@ -162,15 +155,15 @@ func (k Keeper) getExpiringClaims(ctx sdk.Context) (expiringClaims []prooftypes.
// If it is not, the claim will be settled without a proof.
// If it is, the claim will only be settled if a valid proof is available.
// TODO_BLOCKER(@bryanchriswhite, #419): Document safety assumptions of the probabilistic proofs mechanism.
func (k Keeper) isProofRequiredForClaim(_ sdk.Context, claim *prooftypes.Claim) bool {
func (k Keeper) isProofRequiredForClaim(ctx sdk.Context, claim *prooftypes.Claim) bool {
// NB: Assumption that claim is non-nil and has a valid root sum because it
// is retrieved from the store and validated, on-chain, at time of creation.
root := (smt.MerkleRoot)(claim.GetRootHash())
claimComputeUnits := root.Sum()
// TODO_BLOCKER(@Olshansk, #419): This is just VERY BASIC placeholder logic to have something
// TODO_BLOCKER(@bryanchriswhite, #419): This is just VERY BASIC placeholder logic to have something
// in place while we implement proper probabilistic proofs. If you're reading it,
// do not overthink it and look at the documents linked in #419.
if claimComputeUnits < ProofRequiredComputeUnits {
if claimComputeUnits < k.proofKeeper.GetParams(ctx).ProofRequirementThreshold {
return false
}
return true
Expand Down
9 changes: 9 additions & 0 deletions x/tokenomics/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

apptypes "github.com/pokt-network/poktroll/x/application/types"
prooftypes "github.com/pokt-network/poktroll/x/proof/types"
sharedtypes "github.com/pokt-network/poktroll/x/shared/types"
)

// AccountKeeper defines the expected interface for the Account module.
Expand Down Expand Up @@ -41,4 +42,12 @@ type ProofKeeper interface {
// Only used for testing & simulation
UpsertClaim(ctx context.Context, claim prooftypes.Claim)
UpsertProof(ctx context.Context, claim prooftypes.Proof)

GetParams(ctx context.Context) prooftypes.Params
SetParams(ctx context.Context, params prooftypes.Params) error
}

type SharedKeeper interface {
GetParams(ctx context.Context) sharedtypes.Params
SetParams(ctx context.Context, params sharedtypes.Params) error
}

0 comments on commit d586eaa

Please sign in to comment.