Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Supplier] Initial slashing implementation #795

Merged
merged 21 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c9f2aac
feat: Initial slashing implementation
red-0ne Sep 4, 2024
ac4ea35
Merge branch 'main' into feat/slash-missing-proofs
Olshansk Sep 5, 2024
3a6509a
Merge remote-tracking branch 'origin/main' into feat/slash-missing-pr…
red-0ne Sep 10, 2024
6ae5856
fix: Use claimed amount in place of compute units
red-0ne Sep 10, 2024
5e56120
Merge remote-tracking branch 'origin/main' into feat/slash-missing-pr…
red-0ne Sep 11, 2024
b9d032b
refactor: Proof requirement threshold as coin
red-0ne Sep 11, 2024
3a298d6
feat: Add supplier slashing event
red-0ne Sep 11, 2024
3cc8107
Merge remote-tracking branch 'origin/main' into feat/slash-missing-pr…
red-0ne Sep 11, 2024
4851c2d
fix: Unused function and err shadow declaration
red-0ne Sep 11, 2024
a6c1678
chore: Revert test table length
red-0ne Sep 11, 2024
efdd002
fix: Remove wrong comment
red-0ne Sep 11, 2024
1672e4d
chore: Address reivew change requests
red-0ne Sep 16, 2024
b92cb4c
Merge remote-tracking branch 'origin/main' into feat/slash-missing-pr…
red-0ne Sep 16, 2024
182e06a
refactor: Move NumComputeUnitsToCoin to tokenomcis package
red-0ne Sep 16, 2024
e088543
chore: Address review change requests
red-0ne Sep 17, 2024
34fbaf2
chore: Update autogenerated code
red-0ne Sep 17, 2024
6dd7c22
refactor: Move compute units to token multiplier to shared module
red-0ne Sep 17, 2024
afa3808
Merge remote-tracking branch 'origin/main' into feat/slash-missing-pr…
red-0ne Sep 17, 2024
5fc6fff
chore: Remove unused function
red-0ne Sep 17, 2024
c521772
Merge remote-tracking branch 'origin/main' into feat/slash-missing-pr…
red-0ne Sep 18, 2024
baea519
chore: Address review change requests
red-0ne Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 96 additions & 60 deletions api/poktroll/proof/params.pulsar.go

Large diffs are not rendered by default.

698 changes: 664 additions & 34 deletions api/poktroll/tokenomics/event.pulsar.go

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions app/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,17 @@ var (
ibcfeetypes.ModuleName,
// chain modules
servicemoduletypes.ModuleName,
gatewaymoduletypes.ModuleName,
applicationmoduletypes.ModuleName,
suppliermoduletypes.ModuleName,
sessionmoduletypes.ModuleName,
proofmoduletypes.ModuleName,
tokenomicsmoduletypes.ModuleName,
// CRITICAL: THE ORDER HERE IS IMPORTANT AND MUST BE CAREFULLY MAINTAINED.
// Gateway, Application and Supplier end blockers should be called after the
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
// tokenomics module end blocker to ensure that the tokenomics module has
// processed all the pending claims, minting, burning or slashing before
// any of the actors has a chance to withdraw their tokens.
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
gatewaymoduletypes.ModuleName,
applicationmoduletypes.ModuleName,
suppliermoduletypes.ModuleName,
sharedmoduletypes.ModuleName,
// this line is used by starport scaffolding # stargate/app/endBlockers
}
Expand Down
6 changes: 4 additions & 2 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,11 @@ genesis:
proof:
params:
proof_request_probability: "0.25"
proof_requirement_threshold: 20
proof_requirement_threshold:
amount: "20000000"
denom: upokt
proof_missing_penalty:
amount: "320"
amount: "320000000"
denom: upokt
proof_submission_fee:
amount: "1000000"
Expand Down
14 changes: 10 additions & 4 deletions e2e/tests/0_settlement.feature
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ Feature: Tokenomics Namespace
And the "application" account for "app1" is staked
And the service "anvil" registered for application "app1" has a compute units per relay of "1"
# Start servicing relays
# Set proof_requirement_threshold to 19 < num_relays (20) * compute_units_per_relay (1)
# Set proof_requirement_threshold to 839 < num_relays (20) * compute_units_per_relay (1) * compute_units_to_tokens_multiplier (42)
# to make sure a proof is required.
And the "proof" module parameters are set as follows
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
| name | value | type |
| relay_difficulty_target_hash | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff | bytes |
| proof_request_probability | 0.25 | float |
| proof_requirement_threshold | 19 | int64 |
| proof_requirement_threshold | 839 | coin |
| proof_missing_penalty | 320 | coin |
| proof_submission_fee | 1000000 | coin |
And the "tokenomics" module parameters are set as follows
| compute_units_to_tokens_multiplier | 42 | int64 |
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
When the supplier "supplier1" has serviced a session with "20" relays for service "anvil" for application "app1"
# Wait for the Claim & Proof lifecycle
And the user should wait for the "proof" module "CreateClaim" Message to be submitted
Expand All @@ -46,14 +48,18 @@ Feature: Tokenomics Namespace
And an account exists for "app1"
And the "application" account for "app1" is staked
And the service "anvil" registered for application "app1" has a compute units per relay of "1"
# Set proof_request_probability to 0 and proof_requirement_threshold to 100 to make sure a proof is not required.
# Set proof_request_probability to 0 and proof_requirement_threshold to
# 421 > num_relays (10) * compute_units_per_relay (1) * compute_units_to_tokens_multiplier (42)
# to make sure a proof is not required.
And the "proof" module parameters are set as follows
| name | value | type |
| relay_difficulty_target_hash | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff | bytes |
| proof_request_probability | 0 | float |
| proof_requirement_threshold | 100 | int64 |
| proof_requirement_threshold | 421 | coin |
| proof_missing_penalty | 320 | coin |
| proof_submission_fee | 1000000 | coin |
And the "tokenomics" module parameters are set as follows
| compute_units_to_tokens_multiplier | 42 | int64 |
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
# Start servicing
When the supplier "supplier1" has serviced a session with "10" relays for service "anvil" for application "app1"
# Wait for the Claim & Proof lifecycle
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/parse_params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (s *suite) newProofMsgUpdateParams(params paramsMap) cosmostypes.Msg {
case prooftypes.ParamProofRequestProbability:
msgUpdateParams.Params.ProofRequestProbability = paramValue.value.(float32)
case prooftypes.ParamProofRequirementThreshold:
msgUpdateParams.Params.ProofRequirementThreshold = uint64(paramValue.value.(int64))
msgUpdateParams.Params.ProofRequirementThreshold = paramValue.value.(*cosmostypes.Coin)
case prooftypes.ParamProofMissingPenalty:
msgUpdateParams.Params.ProofMissingPenalty = paramValue.value.(*cosmostypes.Coin)
case prooftypes.ParamProofSubmissionFee:
Expand Down
6 changes: 4 additions & 2 deletions e2e/tests/session.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ Feature: Session Namespace

Scenario: Supplier completes claim/proof lifecycle for a valid session
Given the user has the pocketd binary installed
# Set proof_requirement_threshold to 4 < num_relays (5) * compute_units_per_relay (1)
# Set proof_requirement_threshold to 209 < num_relays (5) * compute_units_per_relay (1) * compute_units_to_tokens_multiplier (42)
# to make sure a proof is required.
And the "proof" module parameters are set as follows
| name | value | type |
| relay_difficulty_target_hash | ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff | bytes |
| proof_request_probability | 0.25 | float |
| proof_requirement_threshold | 4 | int64 |
| proof_requirement_threshold | 209 | coin |
| proof_missing_penalty | 320 | coin |
| proof_submission_fee | 1000000 | coin |
And the "tokenomics" module parameters are set as follows
| compute_units_to_tokens_multiplier | 42 | int64 |
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
When the supplier "supplier1" has serviced a session with "5" relays for service "anvil" for application "app1"
And the user should wait for the "proof" module "CreateClaim" Message to be submitted
And the user should wait for the "proof" module "ClaimCreated" tx event to be broadcast
Expand Down
4 changes: 2 additions & 2 deletions e2e/tests/update_params.feature
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Feature: Params Namespace
| name | value | type |
| relay_difficulty_target_hash | 00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff | bytes |
| proof_request_probability | 0.1 | float |
| proof_requirement_threshold | 100 | int64 |
| proof_requirement_threshold | 100 | coin |
| proof_missing_penalty | 500 | coin |
| proof_submission_fee | 5000000 | coin |
Then all "proof" module params should be updated
Expand Down Expand Up @@ -91,7 +91,7 @@ Feature: Params Namespace
| tokenomics | /poktroll.tokenomics.MsgUpdateParam | compute_units_to_tokens_multiplier | 68 | int64 |
| proof | /poktroll.proof.MsgUpdateParam | min_relay_difficulty_bits | 12 | int64 |
| proof | /poktroll.proof.MsgUpdateParam | proof_request_probability | 0.1 | float |
| proof | /poktroll.proof.MsgUpdateParam | proof_requirement_threshold | 100 | int64 |
| proof | /poktroll.proof.MsgUpdateParam | proof_requirement_threshold | 100 | coin |
| proof | /poktroll.proof.MsgUpdateParam | proof_missing_penalty | 500 | coin |
| proof | /poktroll.proof.MsgUpdateParam | proof_submission_fee | 5000000 | coin |
| shared | /poktroll.shared.MsgUpdateParam | num_blocks_per_session | 5 | int64 |
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/update_params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func (s *suite) assertExpectedModuleParamsUpdated(moduleName string) {

proofRequirementThreshold, ok := paramsMap[prooftypes.ParamProofRequirementThreshold]
if ok {
params.ProofRequirementThreshold = uint64(proofRequirementThreshold.value.(int64))
params.ProofRequirementThreshold = proofRequirementThreshold.value.(*cosmostypes.Coin)
}

proofMissingPenalty, ok := paramsMap[prooftypes.ParamProofMissingPenalty]
Expand Down
15 changes: 14 additions & 1 deletion pkg/client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,9 @@ type BlockQueryClient interface {
// is necessary to prevent dependency cycles.
type ProofParams interface {
GetProofRequestProbability() float32
GetProofRequirementThreshold() uint64
GetProofRequirementThreshold() *cosmostypes.Coin
GetProofMissingPenalty() *cosmostypes.Coin
GetProofSubmissionFee() *cosmostypes.Coin
}

// ProofQueryClient defines an interface that enables the querying of the
Expand All @@ -355,8 +356,20 @@ type TokenomicsRelayMiningDifficulty interface {
GetTargetHash() []byte
}

// TokenomicsParams is a go interface type which corresponds to the poktroll.tokenomics.Params
// protobuf message. Since the generated go types don't include interface types, this
// is necessary to prevent dependency cycles.
type TokenomicsParams interface {
GetComputeUnitsToTokensMultiplier() uint64
NumComputeUnitsToCoin(numClaimComputeUnits uint64) (cosmostypes.Coin, error)
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
}

// TokenomicsQueryClient defines an interface that enables the querying of the
// on-chain tokenomics information
type TokenomicsQueryClient interface {
GetServiceRelayDifficultyTargetHash(ctx context.Context, serviceId string) (TokenomicsRelayMiningDifficulty, error)
// GetParams queries the chain for the current tokenomics module parameters.
GetParams(ctx context.Context) (TokenomicsParams, error)
}

// ServiceQueryClient defines an interface that enables the querying of the
Expand Down
14 changes: 14 additions & 0 deletions pkg/client/query/tokenomicsquerier.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,17 @@ func (tq *tokenomicsQuerier) GetServiceRelayDifficultyTargetHash(
}
return &res.RelayMiningDifficulty, nil
}

// GetParams queries & returns the tokenomics module on-chain parameters.
//
// TODO_TECHDEBT(#543): We don't really want to have to query the params for every method call.
// Once `ModuleParamsClient` is implemented, use its replay observable's `#Last()` method
// to get the most recently (asynchronously) observed (and cached) value.
func (sq *tokenomicsQuerier) GetParams(ctx context.Context) (client.TokenomicsParams, error) {
req := &tokenomicstypes.QueryParamsRequest{}
res, err := sq.tokenomicsQuerier.Params(ctx, req)
if err != nil {
return nil, ErrQuerySessionParams.Wrapf("[%v]", err)
}
return &res.Params, nil
}
5 changes: 5 additions & 0 deletions pkg/relayer/session/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ func (rs *relayerSessionsManager) newMapClaimSessionsFn(
return either.Success(sessionTrees), false
}

// TODO_FOLLOWUP(@red-0ne): Ensure that the supplier operator account
// has enough funds to cover for any potential proof submission in order to
// avoid slashing due to missing proofs.
// We should order the claimMsgs by reward amount and include claims up to
// whatever the supplier can afford to cover.
claimMsgs := make([]client.MsgCreateClaim, len(sessionTrees))
for idx, sessionTree := range sessionTrees {
claimMsgs[idx] = &prooftypes.MsgCreateClaim{
Expand Down
19 changes: 15 additions & 4 deletions pkg/relayer/session/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,25 @@ func (rs *relayerSessionsManager) isProofRequired(
return false, err
}

tokenomicsParams, err := rs.tokenomicsQueryClient.GetParams(ctx)
if err != nil {
return false, err
}

// The amount of uPOKT being claimed.
claimedAmount, err := tokenomicsParams.NumComputeUnitsToCoin(numClaimComputeUnits)
if err != nil {
return false, err
}

logger = logger.With(
"num_claim_compute_units", numClaimComputeUnits,
"proof_requirement_threshold", proofParams.GetProofRequirementThreshold(),
"claimed_amount_upokt", claimedAmount.Amount.Uint64(),
"proof_requirement_threshold_upokt", proofParams.GetProofRequirementThreshold(),
)

// Require a proof if the claim's compute units meets or exceeds the threshold.
// Require a proof if the claimed amount meets or exceeds the threshold.
// TODO_MAINNET: This should be proportional to the supplier's stake as well.
if numClaimComputeUnits >= proofParams.GetProofRequirementThreshold() {
if claimedAmount.Amount.GTE(proofParams.GetProofRequirementThreshold().Amount) {
logger.Info().Msg("compute units is above threshold, claim requires proof")

return true, nil
Expand Down
4 changes: 4 additions & 0 deletions pkg/relayer/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ type relayerSessionsManager struct {
// requirement probability governance parameters to determine whether a submitted
// claim requires a proof.
proofQueryClient client.ProofQueryClient

// tokenomicsQueryClient is used to query for the tokenomics module parameters.
tokenomicsQueryClient client.TokenomicsQueryClient
}

// NewRelayerSessions creates a new relayerSessions.
Expand Down Expand Up @@ -94,6 +97,7 @@ func NewRelayerSessions(
&rs.sharedQueryClient,
&rs.serviceQueryClient,
&rs.proofQueryClient,
&rs.tokenomicsQueryClient,
); err != nil {
return nil, err
}
Expand Down
15 changes: 10 additions & 5 deletions pkg/relayer/session/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import (
"time"

"cosmossdk.io/depinject"
sdkmath "cosmossdk.io/math"
coretypes "github.com/cometbft/cometbft/rpc/core/types"
cmttypes "github.com/cometbft/cometbft/types"
sdktypes "github.com/cosmos/cosmos-sdk/types"
"github.com/golang/mock/gomock"
"github.com/pokt-network/smt"
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/app/volatile"
"github.com/pokt-network/poktroll/pkg/client"
"github.com/pokt-network/poktroll/pkg/observable/channel"
"github.com/pokt-network/poktroll/pkg/polylog/polyzero"
Expand Down Expand Up @@ -103,6 +106,7 @@ func requireProofCountEqualsExpectedValueFromProofParams(t *testing.T, proofPara

serviceQueryClientMock := testqueryclients.NewTestServiceQueryClient(t)
proofQueryClientMock := testqueryclients.NewTestProofQueryClientWithParams(t, &proofParams)
tokenomicsQueryClient := testqueryclients.NewTestTokenomicsQueryClient(t)

deps := depinject.Supply(
blockClient,
Expand All @@ -111,6 +115,7 @@ func requireProofCountEqualsExpectedValueFromProofParams(t *testing.T, proofPara
sharedQueryClientMock,
serviceQueryClientMock,
proofQueryClientMock,
tokenomicsQueryClient,
)
storesDirectoryOpt := testrelayer.WithTempStoresDirectory(t)

Expand Down Expand Up @@ -192,7 +197,7 @@ func TestRelayerSessionsManager_ProofThresholdRequired(t *testing.T) {
proofParams := prooftypes.DefaultParams()

// Set proof requirement threshold to a low enough value so a proof is always requested.
proofParams.ProofRequirementThreshold = 1
proofParams.ProofRequirementThreshold = &sdktypes.Coin{Denom: volatile.DenomuPOKT, Amount: sdkmath.NewInt(1)}

// The test is submitting a single claim. Having the proof requirement threshold
// set to 1 results in exactly 1 proof being requested.
Expand All @@ -204,8 +209,8 @@ func TestRelayerSessionsManager_ProofThresholdRequired(t *testing.T) {
func TestRelayerSessionsManager_ProofProbabilityRequired(t *testing.T) {
proofParams := prooftypes.DefaultParams()

// Set proof requirement threshold to max uint64 to skip the threshold check.
proofParams.ProofRequirementThreshold = math.MaxUint64
// Set proof requirement threshold to max int64 to skip the threshold check.
proofParams.ProofRequirementThreshold = &sdktypes.Coin{Denom: volatile.DenomuPOKT, Amount: sdkmath.NewInt(math.MaxInt64)}
// Set proof request probability to 1 so a proof is always requested.
proofParams.ProofRequestProbability = 1

Expand All @@ -219,8 +224,8 @@ func TestRelayerSessionsManager_ProofProbabilityRequired(t *testing.T) {
func TestRelayerSessionsManager_ProofNotRequired(t *testing.T) {
proofParams := prooftypes.DefaultParams()

// Set proof requirement threshold to max uint64 to skip the threshold check.
proofParams.ProofRequirementThreshold = math.MaxUint64
// Set proof requirement threshold to max int64 to skip the threshold check.
proofParams.ProofRequirementThreshold = &sdktypes.Coin{Denom: volatile.DenomuPOKT, Amount: sdkmath.NewInt(math.MaxInt64)}
// Set proof request probability to 0 so a proof is never requested.
proofParams.ProofRequestProbability = 0

Expand Down
6 changes: 4 additions & 2 deletions proto/poktroll/proof/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,20 @@ message Params {
// is equal to or above the threshold. This is in contrast to the this requirement
// being determined probabilistically via ProofRequestProbability.
//
// TODO_MAINNET: Consider renaming this to `proof_requirement_threshold_compute_units`.
uint64 proof_requirement_threshold = 3 [(gogoproto.jsontag) = "proof_requirement_threshold"];
// TODO_MAINNET: Consider renaming this to `proof_requirement_threshold_upokt`.
cosmos.base.v1beta1.Coin proof_requirement_threshold = 3 [(gogoproto.jsontag) = "proof_requirement_threshold"];
red-0ne marked this conversation as resolved.
Show resolved Hide resolved

// proof_missing_penalty is the number of tokens (uPOKT) which should be slashed from a supplier
// when a proof is required (either via proof_requirement_threshold or proof_missing_penalty)
// but is not provided.
// TODO_MAINNET: Consider renaming this to `proof_missing_penalty_upokt`.
cosmos.base.v1beta1.Coin proof_missing_penalty = 4 [(gogoproto.jsontag) = "proof_missing_penalty"];

// proof_submission_fee is the number of tokens (uPOKT) which should be paid by
// the supplier operator when submitting a proof.
// This is needed to account for the cost of storing proofs on-chain and prevent
// spamming (i.e. sybil bloat attacks) the network with non-required proofs.
// TODO_MAINNET: Consider renaming this to `proof_submission_fee_upokt`.
cosmos.base.v1beta1.Coin proof_submission_fee = 5 [(gogoproto.jsontag) = "proof_submission_fee"];

// IMPORTANT: Make sure to update all related files if you're modifying or adding a new parameter.
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 @@ -59,4 +59,15 @@ message EventApplicationOverserviced {
// is a function of the relay mining algorithm.
// E.g. The application's stake divided by the number of suppliers in a session.
cosmos.base.v1beta1.Coin effective_burn = 4;
}

// EventSupplierSlashed is emitted when a supplier is slashed for not providing,
// or provided invalid required proofs for claims.
message EventSupplierSlashed {
string supplier_operator_addr = 1;
// Number of claims that were expired and led to the slashing.
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
uint64 num_expired_claims = 2;
// 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;
}
Loading
Loading