Skip to content

Commit

Permalink
[Tokenomics] Preparation for Global Mint Reimbursement Request (#755)
Browse files Browse the repository at this point in the history
## Summary

Implement "max claimable amount" in preparation for "global mint
reimbursement request".

Specific changes:
- Added lots of `TODO_BETA` in preparation for beta testnet
- Added a lot more comments and explanation in the TLM processors

## Issue

- #732
- Related to #750
- Superceed #740

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: Bryan White <[email protected]>
Co-authored-by: Redouane Lakrache <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Sep 9, 2024
1 parent 9e8aa58 commit 70a2bf4
Show file tree
Hide file tree
Showing 26 changed files with 1,084 additions and 614 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
context: .

run-e2e-tests:
timeout-minutes: 20
timeout-minutes: 30
needs: build-push-container
if: contains(github.event.pull_request.labels.*.name, 'devnet-test-e2e')
runs-on: ubuntu-latest
Expand Down
6 changes: 3 additions & 3 deletions api/poktroll/shared/service.pulsar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

169 changes: 126 additions & 43 deletions api/poktroll/tokenomics/event.pulsar.go

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ accounts:
mnemonic: "involve clean slab term real human green immune valid swing protect talk silent unique cart few ice era right thunder again drop among bounce"
coins:
- 300000000upokt
- name: apptiny
mnemonic: "worry pupil rival such jump pitch flame prosper tattoo eternal round receive cube crowd remove afraid garment brand toy nut guitar toy sausage fragile"
coins:
- 1000000upokt # 1 POKT
- name: supplier1
mnemonic: "cool industry busy tumble funny relax error state height like board wing goat emerge visual idle never unveil announce hill primary okay spatial frog"
coins:
Expand Down Expand Up @@ -176,6 +180,20 @@ genesis:
# `supplier1_stake_config.yaml` so that the stake command causes a state change.
amount: "1000068"
denom: upokt
- address: pokt1ad28jdap2zfanjd7hpkh984yveney6k9a42man
delegatee_gateway_addresses: []
service_configs:
- service:
id: anvil
- service:
id: rest
- service:
id: ollama
stake:
# NB: This value should be exactly 1upokt smaller than the value in
# `supplier1_stake_config.yaml` so that the stake command causes a state change.
amount: "1000068"
denom: upokt
supplier:
supplierList:
- owner_address: pokt19a3t4yunp0dlpfjrp7qwnzwlrzd5fzs2gjaaaj
Expand Down
33 changes: 21 additions & 12 deletions e2e/tests/0_settlement.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,38 @@
# that can be used to clear the state of the chain between tests.

Feature: Tokenomics Namespace

Scenario: Emissions equals burn when a claim is created and a valid proof is submitted and required via threshold
Scenario: Settle the session 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
# Network preparation and validation
And an account exists for "supplier1"
And the "supplier" account for "supplier1" is staked
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"
# Start servicing
# Set proof_requirement_threshold to 9 < num_relays (10) * compute_units_per_relay (1)
# Start servicing relays
# Set proof_requirement_threshold to 19 < num_relays (20) * compute_units_per_relay (1)
# 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 | 9 | int64 |
| proof_requirement_threshold | 19 | int64 |
| proof_missing_penalty | 320 | coin |
| proof_submission_fee | 1000000 | coin |
When the supplier "supplier1" has serviced a session with "10" relays for service "anvil" for application "app1"
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
And the user should wait for the "proof" module "SubmitProof" Message to be submitted
And the user should wait for the ClaimSettled event with "THRESHOLD" proof requirement to be broadcast
# Validate the results
Then the account balance of "supplier1" should be "420" uPOKT "more" than before
And the "application" stake of "app1" should be "420" uPOKT "less" than before
# 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
# 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

Scenario: Emissions equals burn when a claim is created but a proof is not required
Scenario: Settle the session when a valid claim is create but not required
# Baseline
Given the user has the pocketd binary installed
# Network preparation
Expand All @@ -59,11 +61,18 @@ Feature: Tokenomics Namespace
# No proof should be submitted, don't wait for one.
And the user should wait for the ClaimSettled event with "NOT_REQUIRED" proof requirement to be broadcast
# Validate the results
Then the account balance of "supplier1" should be "420" uPOKT "more" than before
# 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
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

# TODO_ADDTEST: Implement the following scenarios
# Scenario: Emissions equals burn when a claim is created and a valid proof is submitted but not required
# Scenario: Supplier revenue shares are properly distributed
# Scenario: TLM Mint=Burn when a valid claim is outside Max Limits
# - Ensure over serviced event is submitted
# Scenario: TLM GlobalMint properly distributes minted rewards to all actors
# - Ensure reimbursement request is submitted
# Scenario: Mint equals burn when a claim is created and a valid proof is submitted but not required
# Scenario: No emissions or burn when a claim is created and an invalid proof is submitted
# Scenario: No emissions or burn when a claim is created and a proof is required but is not submitted
# Scenario: No emissions or burn when no claim is created
1 change: 0 additions & 1 deletion e2e/tests/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,6 @@ func (s *suite) getConfigFileContent(
default:
s.Fatalf("ERROR: unknown actor type %s", actorType)
}
fmt.Println(yaml.NormalizeYAMLIndentation(configContent))
return yaml.NormalizeYAMLIndentation(configContent)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/crypto/rings/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func newAccount(curve string) account {
var pubkey cryptotypes.PubKey
switch curve {
case "ed25519":
addr, pubkey = sample.AccAddressAndPubKeyEdd2519()
addr, pubkey = sample.AccAddressAndPubKeyEd25519()
case "secp256k1":
addr, pubkey = sample.AccAddressAndPubKey()
}
Expand Down
1 change: 1 addition & 0 deletions pkg/relayer/proxy/relay_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (rp *relayerProxy) VerifyRelayRequest(
}).
Msg("verifying relay request session")

// TODO_TECHDEBT(@red-0ne): Optimize this so we don't have to query the session for every relay request.
// Query for the current session to check if relayRequest sessionId matches the current session.
session, err := rp.sessionQuerier.GetSession(
ctx,
Expand Down
17 changes: 16 additions & 1 deletion pkg/relayer/session/sessiontree.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ import (
var _ relayer.SessionTree = (*sessionTree)(nil)

// sessionTree is an implementation of the SessionTree interface.
// TODO_TEST: Add tests to the sessionTree.
// TODO_BETA(@red-0ne): Per the Relay Mining paper, we need to optimistically store
// the number of requests that an application can pay for. This needs to be tracked
// based on the app's stake in the beginning of a session and the number of nodes
// per session. An operator should be able to specify "overservicing_compute_units_limit"
// whereby an upper bound on how much it can overservice an application is set. The
// default value for this should be -1, implying "unlimited".
// Ref discussion: https://github.com/pokt-network/poktroll/pull/755#discussion_r1737287860
type sessionTree struct {
// sessionMu is a mutex used to protect sessionTree operations from concurrent access.
sessionMu *sync.Mutex
Expand Down Expand Up @@ -65,6 +71,15 @@ type sessionTree struct {
// NewSessionTree creates a new sessionTree from a Session and a storePrefix. It also takes a function
// removeFromRelayerSessions that removes the sessionTree from the RelayerSessionsManager.
// It returns an error if the KVStore fails to be created.
//
// TODO_BETA(@red-0ne): When starting a new session, check what the MaxClaimableAmount
// (in uPOKT) by the Supplier as a function of
// (app_stake, compute_units_per_relay_for_service, global_compute_units_to_token_multiplier).
// TODO_CONFIG_NOTE: Whether or not the RelayMiner stop handling requests when the max is reached should be
// configurable by the operator.
// TODO_ERROR_NOTE: If overservicing is set to false, create a new error that the relay is rejected
// specifically because the supplier has reached the max claimable amount, so the caller should relay
// the request to another supplier.
func NewSessionTree(
sessionHeader *sessiontypes.SessionHeader,
supplierOperatorAddress *cosmostypes.AccAddress,
Expand Down
6 changes: 3 additions & 3 deletions proto/poktroll/shared/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ message Service {
// For example, what if we want to request a session for a certain service but with some additional configs that identify it?
string id = 1; // Unique identifier for the service

// TODO_MAINNET: Remove this.
// TODO_BETA: Either remove this or rename it to alias.
string name = 2; // (Optional) Semantic human readable name for the service

// The cost of a single relay for this service in terms of compute units.
Expand All @@ -32,7 +32,7 @@ message Service {

// ApplicationServiceConfig holds the service configuration the application stakes for
message ApplicationServiceConfig {
// TODO_MAINNET: Avoid embedding the full Service because we just need the ID.
// TODO_BETA: Avoid embedding the full Service because we just need the ID.
Service service = 1; // The Service for which the application is configured

// TODO_MAINNET: There is an opportunity for applications to advertise the max
Expand All @@ -42,7 +42,7 @@ message ApplicationServiceConfig {

// SupplierServiceConfig holds the service configuration the supplier stakes for
message SupplierServiceConfig {
// TODO_MAINNET: Avoid embedding the full Service because we just need the ID.
// TODO_BETA: Avoid embedding the full Service because we just need the ID.
Service service = 1; // The Service for which the supplier is configured
repeated SupplierEndpoint endpoints = 2; // List of endpoints for the service
repeated ServiceRevenueShare rev_share = 3; // List of revenue share configurations for the service
Expand Down
21 changes: 15 additions & 6 deletions proto/poktroll/tokenomics/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ package poktroll.tokenomics;
option go_package = "github.com/pokt-network/poktroll/x/tokenomics/types";
option (gogoproto.stable_marshaler_all) = true;

import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "poktroll/proof/types.proto";

enum ClaimExpirationReason {
Expand Down Expand Up @@ -44,10 +44,19 @@ message EventRelayMiningDifficultyUpdated {
uint64 new_num_relays_ema = 5;
}

// EventApplicationOverserviced is emitted when an application has less stake
// than the expected burn.
// EventApplicationOverserviced is emitted when an application has less stake than
// what a supplier is claiming (i.e. amount available for burning is insufficient).
// This means the following will ALWAYS be strictly true: effective_burn < expected_burn.
message EventApplicationOverserviced {
string application_addr = 1;
cosmos.base.v1beta1.Coin expected_burn = 2;
cosmos.base.v1beta1.Coin effective_burn = 3;
}
string supplier_operator_addr = 2;
// Expected burn is the amount the supplier is claiming for work done
// to service the application during the session.
// This is usually the amount in the Claim submitted.
cosmos.base.v1beta1.Coin expected_burn = 3;
// Effective burn is the amount that is actually being paid to the supplier
// for the work done. It is less than the expected burn (claim amount) and
// 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;
}
46 changes: 25 additions & 21 deletions tests/integration/tokenomics/relay_mining_difficulty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ func TestUpdateRelayMiningDifficulty_NewServiceSeenForTheFirstTime(t *testing.T)
session := getSession(t, integrationApp)
sharedParams := getSharedParams(t, integrationApp)

// Prepare the trie with a single mined relay
trie := prepareSMST(t, sdkCtx, integrationApp, session)
// Prepare the trie with several mined relays
expectedNumRelays := uint64(100)
trie := prepareSMST(t, sdkCtx, integrationApp, session, expectedNumRelays)

// Compute the number of blocks to wait between different events
// TODO_BLOCKER(@bryanchriswhite): See this comment: https://github.com/pokt-network/poktroll/pull/610#discussion_r1645777322
Expand Down Expand Up @@ -118,8 +119,8 @@ func TestUpdateRelayMiningDifficulty_NewServiceSeenForTheFirstTime(t *testing.T)
require.Equal(t, prooftypes.DefaultRelayDifficultyTargetHashHex, relayMiningEvent.NewTargetHashHexEncoded)

// The previous EMA is the same as the current one if the service is new
require.Equal(t, uint64(1), relayMiningEvent.PrevNumRelaysEma)
require.Equal(t, uint64(1), relayMiningEvent.NewNumRelaysEma)
require.Equal(t, expectedNumRelays, relayMiningEvent.PrevNumRelaysEma)
require.Equal(t, expectedNumRelays, relayMiningEvent.NewNumRelaysEma)
}

func UpdateRelayMiningDifficulty_UpdatingMultipleServicesAtOnce(t *testing.T) {}
Expand Down Expand Up @@ -164,11 +165,12 @@ func getSession(t *testing.T, integrationApp *testutil.App) *sessiontypes.Sessio
return getSessionRes.Session
}

// prepareSMST prepares an SMST with a single mined relay for the given session.
// prepareSMST prepares an SMST with the given number of mined relays.
func prepareSMST(
t *testing.T, ctx context.Context,
integrationApp *testutil.App,
session *sessiontypes.Session,
numRelays uint64,
) *smt.SMST {
t.Helper()

Expand All @@ -178,23 +180,25 @@ func prepareSMST(
kvStore, err := pebble.NewKVStore("")
require.NoError(t, err)

// NB: A signed mined relay is a MinedRelay type with the appropriate
// payload, signatures and metadata populated.
//
// It does not (as of writing) adhere to the actual on-chain difficulty (i.e.
// hash check) of the test service surrounding the scope of this test.
minedRelay := testrelayer.NewSignedMinedRelay(t, ctx,
session,
integrationApp.DefaultApplication.Address,
integrationApp.DefaultSupplier.OperatorAddress,
integrationApp.DefaultSupplierKeyringKeyringUid,
integrationApp.GetKeyRing(),
integrationApp.GetRingClient(),
)

trie := smt.NewSparseMerkleSumTrie(kvStore, protocol.NewTrieHasher(), smt.WithValueHasher(nil))
err = trie.Update(minedRelay.Hash, minedRelay.Bytes, 1)
require.NoError(t, err)

for i := uint64(0); i < numRelays; i++ {
// DEV_NOTE: A signed mined relay is a MinedRelay type with the appropriate
// payload, signatures and metadata populated.
// It does not (as of writing) adhere to the actual on-chain difficulty (i.e.
// hash check) of the test service surrounding the scope of this test.
minedRelay := testrelayer.NewSignedMinedRelay(t, ctx,
session,
integrationApp.DefaultApplication.Address,
integrationApp.DefaultSupplier.OperatorAddress,
integrationApp.DefaultSupplierKeyringKeyringUid,
integrationApp.GetKeyRing(),
integrationApp.GetRingClient(),
)

err = trie.Update(minedRelay.Hash, minedRelay.Bytes, 1)
require.NoError(t, err)
}

return trie
}
Expand Down
2 changes: 1 addition & 1 deletion testutil/integration/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,8 +517,8 @@ func NewCompleteIntegrationApp(t *testing.T) *App {
defaultService := sharedtypes.Service{
Id: "svc1",
Name: "svcName1",
OwnerAddress: sample.AccAddress(),
ComputeUnitsPerRelay: 1,
OwnerAddress: sample.AccAddress(),
}
serviceKeeper.SetService(integrationApp.sdkCtx, defaultService)
integrationApp.DefaultService = &defaultService
Expand Down
16 changes: 16 additions & 0 deletions testutil/keeper/tokenomics.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ func TokenomicsKeeperWithActorAddrs(t testing.TB) (
mockBankKeeper.EXPECT().
SendCoinsFromModuleToAccount(gomock.Any(), tokenomicstypes.ModuleName, gomock.Any(), gomock.Any()).
AnyTimes()
mockBankKeeper.EXPECT().
SendCoinsFromModuleToModule(gomock.Any(), tokenomicstypes.ModuleName, suppliertypes.ModuleName, gomock.Any()).
AnyTimes()

// Mock the account keeper
mockAccountKeeper := mocks.NewMockAccountKeeper(ctrl)
Expand Down Expand Up @@ -459,3 +462,16 @@ func WithService(service sharedtypes.Service) TokenomicsModuleKeepersOpt {
return ctx
}
}

func WithProposerAddr(addr string) TokenomicsModuleKeepersOpt {
return func(ctx context.Context, keepers *TokenomicsModuleKeepers) context.Context {
valAddr, err := cosmostypes.ValAddressFromBech32(addr)
if err != nil {
panic(err)
}
consensusAddr := cosmostypes.ConsAddress(valAddr)
sdkCtx := sdk.UnwrapSDKContext(ctx)
sdkCtx = sdkCtx.WithProposer(consensusAddr)
return sdkCtx
}
}
Loading

0 comments on commit 70a2bf4

Please sign in to comment.