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

[RelayMiner] Supplier rate limiting #895

Merged
merged 80 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
6738d21
WIP
Olshansk Aug 16, 2024
3e6a146
Merge branch 'main' into inflation
Olshansk Aug 20, 2024
c3a235a
Checkpoint
Olshansk Aug 21, 2024
846bc1d
Finished first version of the documentation
Olshansk Aug 21, 2024
40c56a5
Merge branch 'main' into inflation
Olshansk Aug 21, 2024
98736f3
Revert tokenomic docs
Olshansk Aug 21, 2024
eeabb30
Performed self review
Olshansk Aug 21, 2024
10ef513
Merge with main
Olshansk Aug 22, 2024
0e725a4
Remove everything related to TLMGlobalMintReimbursementRequest
Olshansk Aug 22, 2024
bbe2366
WIP
Olshansk Aug 23, 2024
ad70e70
Fixed the TestProcessTokenLogicModules_TLMBurnEqualsMintValid test
Olshansk Aug 23, 2024
b02ef24
WIP
Olshansk Aug 24, 2024
8663fac
Finished implementing TestProcessTokenLogicModules_TLMBurnEqualsMint_…
Olshansk Aug 25, 2024
085d134
Implemented TestProcessTokenLogicModules_TLMGlobalMint_Valid_MintDist…
Olshansk Aug 25, 2024
1de13ba
Updated comments in e2e/tests/0_settlement.feature
Olshansk Aug 25, 2024
b8dff51
Fixed failing unit test
Olshansk Aug 25, 2024
9231d6f
Merge branch 'main' into issues/732_max_claimable_pokt
Olshansk Aug 25, 2024
f79989a
Merge branch 'main' into issues/732_max_claimable_pokt
Olshansk Aug 26, 2024
4c335e7
update compile proto
Olshansk Aug 26, 2024
0020303
Merge remote-tracking branch 'origin/main' into issues/732_tokenomics…
red-0ne Sep 19, 2024
f7a0da7
fix: Remove merge added lines
red-0ne Sep 19, 2024
79ac3e6
feat: Add GMRR
red-0ne Sep 20, 2024
1befc5e
fix: Skip GMRR if GMI is disabled
red-0ne Sep 20, 2024
e5a732b
Merge remote-tracking branch 'origin/main' into issues/732_tokenomics…
red-0ne Oct 13, 2024
197c5a0
fix: Activate GMRR TLM
red-0ne Oct 14, 2024
61a62fa
chore: Add TODO for the relay miner to account for GMRR
red-0ne Oct 14, 2024
942c525
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 14, 2024
5db96bc
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 14, 2024
13784b8
fix: Min stake tests
red-0ne Oct 14, 2024
2be5464
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 14, 2024
3baec62
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 17, 2024
e91a20e
feat: Add WithProofRequirement option
red-0ne Oct 18, 2024
568ecb9
fix: ensure app stake is always enough
red-0ne Oct 24, 2024
6c836e7
fix: Ensure no over-servicing
red-0ne Oct 24, 2024
fafd2f3
chore: Address reveiw change requests
red-0ne Oct 24, 2024
ba13177
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 25, 2024
3185741
fix: Add globalmint to tests
red-0ne Oct 25, 2024
1593bfb
fix: Linting errors
red-0ne Oct 25, 2024
b2a14b2
fix: Check cached stake before query
red-0ne Oct 25, 2024
5e6e514
fix: Remove redundant check
red-0ne Oct 25, 2024
2b14bab
feat: Supplier rate limiting
red-0ne Oct 28, 2024
f050d8c
fix: Cmd dependencies
red-0ne Oct 28, 2024
196c2fa
fix: Hidden error
red-0ne Oct 28, 2024
6fea32f
fix: Ineffectual assignement
red-0ne Oct 28, 2024
b175916
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 28, 2024
7bd6058
chore: Address review change requests
red-0ne Oct 29, 2024
3f1a2b8
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Oct 29, 2024
f304c87
feat: Add overservicing allowance
red-0ne Oct 29, 2024
aaec275
Merge remote-tracking branch 'origin/feat/gmrr' into feat/no-overserv…
red-0ne Oct 29, 2024
2ee8448
fix: Merge error
red-0ne Oct 29, 2024
6170a45
improve: Add per relayer app meter
red-0ne Oct 30, 2024
2784cfb
Merge remote-tracking branch 'origin/main' into feat/gmrr
red-0ne Oct 30, 2024
2bd7240
chore: Address review change requests
red-0ne Oct 30, 2024
4c65e84
Merge remote-tracking branch 'origin/feat/gmrr' into feat/no-overserv…
red-0ne Oct 30, 2024
ee93efa
fix: Rename supplier app stake retrieval fn
red-0ne Oct 30, 2024
1550475
fix: Update godoc comment
red-0ne Oct 30, 2024
cbe98ac
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Oct 30, 2024
cd45da6
chore: Address reivew change requests
red-0ne Oct 30, 2024
5ad3b28
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Oct 31, 2024
e7c288b
fix: Total supply
red-0ne Oct 31, 2024
44a5b19
fix: Add all apps suply
red-0ne Oct 31, 2024
d38c51c
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Nov 5, 2024
c5a3c20
Empty commit
red-0ne Nov 5, 2024
1146aba
chore: Address review change requests
red-0ne Nov 6, 2024
7b8998e
fix: Do not process 0 claim amounts
red-0ne Nov 6, 2024
88a3d45
Empty commit
red-0ne Nov 6, 2024
4b04353
fix: Prevent 0 settlement due to int div
red-0ne Nov 7, 2024
331654f
fix: consumed coin calculation
red-0ne Nov 7, 2024
1cb995c
chore: Address review change requests
red-0ne Nov 11, 2024
a39d5b2
fix: Remove wrong param comment
red-0ne Nov 11, 2024
a2dd722
chore: Remove already tackled TODOs
red-0ne Nov 11, 2024
2b64d0b
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Nov 11, 2024
586b20e
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Nov 12, 2024
75fc9b6
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Nov 13, 2024
b46cb72
fix: Merge with main
red-0ne Nov 13, 2024
e7d14ba
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Nov 13, 2024
02e40fb
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Nov 13, 2024
990ef42
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Nov 13, 2024
8d491ce
fix: Missing session querier
red-0ne Nov 13, 2024
a800fbd
Merge remote-tracking branch 'origin/main' into feat/no-overservicing
red-0ne Nov 13, 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
3 changes: 2 additions & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ genesis:
# Application module
- address: pokt1rl3gjgzexmplmds3tq3r3yk84zlwdl6djzgsvm
coins:
# TODO_MAINNET: Pass config.yml into ChatGPT to build a script that ensures the amounts line up
# TODO_MAINNET(@olshansk): Pass config.yml into ChatGPT to build a script
# that ensures the amounts line up
- amount: "200000136" # MUST BE equal to the total of all app stakes below
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
denom: upokt
# Supplier module
Expand Down
27 changes: 17 additions & 10 deletions pkg/relayer/miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,8 @@ func (mnr *miner) mapMineRelay(
) (_ either.Either[*relayer.MinedRelay], skip bool) {
relayBz, err := relay.Marshal()
if err != nil {
meteringError := mnr.relayMeter.SetNonApplicableRelayReward(ctx, relay.GetReq().GetMeta())
if meteringError != nil {
return either.Error[*relayer.MinedRelay](fmt.Errorf("failed to unclaim relay price: %w", meteringError)), false
if relayMeteringResult := mnr.unclaimRelayUPOKT(ctx, *relay); relayMeteringResult.IsError() {
return relayMeteringResult, false
}
return either.Error[*relayer.MinedRelay](err), false
}
Expand All @@ -100,18 +99,16 @@ func (mnr *miner) mapMineRelay(

relayDifficultyTargetHash, err := mnr.getServiceRelayDifficultyTargetHash(ctx, relay.Req)
if err != nil {
meteringError := mnr.relayMeter.SetNonApplicableRelayReward(ctx, relay.GetReq().GetMeta())
if meteringError != nil {
return either.Error[*relayer.MinedRelay](fmt.Errorf("failed to unclaim relay price: %w", meteringError)), false
if relayMeteringResult := mnr.unclaimRelayUPOKT(ctx, *relay); relayMeteringResult.IsError() {
return relayMeteringResult, true
}
return either.Error[*relayer.MinedRelay](err), false
return either.Error[*relayer.MinedRelay](err), true
}

// The relay IS NOT volume / reward applicable
if !protocol.IsRelayVolumeApplicable(relayHash, relayDifficultyTargetHash) {
meteringError := mnr.relayMeter.SetNonApplicableRelayReward(ctx, relay.GetReq().GetMeta())
if meteringError != nil {
return either.Error[*relayer.MinedRelay](fmt.Errorf("failed to unclaim relay price: %w", meteringError)), false
if eitherMeteringResult := mnr.unclaimRelayUPOKT(ctx, *relay); eitherMeteringResult.IsError() {
return eitherMeteringResult, true
}
return either.Success[*relayer.MinedRelay](nil), true
}
Expand Down Expand Up @@ -148,3 +145,13 @@ func (mnr *miner) getServiceRelayDifficultyTargetHash(ctx context.Context, req *

return serviceRelayDifficulty.GetTargetHash(), nil
}

// unclaimRelayUPOKT unclaims the relay UPOKT reward for the relay.
// It returns an either.Error if the relay UPOKT reward could not be unclaimed.
func (mnr *miner) unclaimRelayUPOKT(ctx context.Context, relay servicetypes.Relay) either.Either[*relayer.MinedRelay] {
if err := mnr.relayMeter.SetNonApplicableRelayReward(ctx, relay.GetReq().GetMeta()); err != nil {
return either.Error[*relayer.MinedRelay](err)
}

return either.Success[*relayer.MinedRelay](nil)
}
8 changes: 4 additions & 4 deletions pkg/relayer/miner/miner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ func TestMiner_MinedRelays(t *testing.T) {

testqueryclients.SetServiceRelayDifficultyTargetHash(t, testSvcId, testRelayMiningTargetHash)
serviceQueryClientMock := testqueryclients.NewTestServiceQueryClient(t)
testRelayMeter := mockRelayMeter(t)
relayMeterMock := newMockRelayMeter(t)

deps := depinject.Supply(serviceQueryClientMock, testRelayMeter)
deps := depinject.Supply(serviceQueryClientMock, relayMeterMock)
mnr, err := miner.NewMiner(deps)
require.NoError(t, err)

Expand Down Expand Up @@ -158,8 +158,8 @@ func unmarshalHexMinedRelay(
}
}

// mockRelayMeter returns a mock RelayMeter that is used by the relay miner to claim and unclaim relays.
func mockRelayMeter(t *testing.T) relayer.RelayMeter {
// newMockRelayMeter returns a mock RelayMeter that is used by the relay miner to claim and unclaim relays.
func newMockRelayMeter(t *testing.T) relayer.RelayMeter {
t.Helper()

ctrl := gomock.NewController(t)
Expand Down
1 change: 1 addition & 0 deletions pkg/relayer/proxy/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ var (
ErrRelayerProxyMissingSupplierOperatorAddress = sdkerrors.Register(codespace, 11, "supplier operator address is missing")
ErrRelayerProxyUnknownSession = sdkerrors.Register(codespace, 12, "relayer proxy encountered unknown session")
ErrRelayerProxyRateLimited = sdkerrors.Register(codespace, 13, "offchain rate limit hit by relayer proxy")
ErrRelayerProxyUnclaimRelayPrice = sdkerrors.Register(codespace, 14, "failed to unclaim relay price")
)
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion pkg/relayer/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ type relayerProxy struct {

// relayMeter keeps track of the total amount of stake an onchhain Application
// will owe an onchain Supplier (backed by this RelayMiner) once the session settles.
// It also configures over servicing allowance.
// It also configures application over-servicing allowance.
relayMeter relayer.RelayMeter
}

Expand Down
35 changes: 19 additions & 16 deletions pkg/relayer/proxy/relay_meter.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ type sessionRelayMeter struct {
sessionHeader *sessiontypes.SessionHeader
// numOverServicedRelays is the number of relays that have been over-serviced
// by the relayer for the application.
numOverServicedRelays int
numOverServicedRelays uint64
// numOverservicedComputeUnits is the number of compute units that have been
// over-serviced by the relayer for the application.
numOverServicedComputeUnits uint64

// sharedParams, service and serviceRelayDifficulty are used to calculate the relay cost
// that increments the consumedAmount.
Expand All @@ -59,6 +62,8 @@ type sessionRelayMeter struct {
// It ensures that no Application is over-serviced by the Supplier per session.
// This is done by maintaining the max amount of stake the supplier can consume
// per session and the amount of stake consumed by mined relays.
// TODO_POST_MAINNET(@red-0ne): Consider making the relay meter a light client,
// since it's already receiving all committed blocks and events.
type ProxyRelayMeter struct {
// sessionToRelayMeterMap is a map of session IDs to their corresponding session relay meter.
// Only known applications (i.e. have sent at least one relay) have their stakes metered.
Expand Down Expand Up @@ -138,7 +143,7 @@ func (rmtr *ProxyRelayMeter) Start(ctx context.Context) error {
// The relay reward is added optimistically, assuming that the relay will be volume / reward
// applicable and the relay meter would remain up to date.
func (rmtr *ProxyRelayMeter) AccumulateRelayReward(ctx context.Context, reqMeta servicetypes.RelayRequestMetadata) error {
// TODO_MAINNET: Locking the relay serving flow to ensure that the relay meter is updated
// TODO_MAINNET(@adshmh): Locking the relay serving flow to ensure that the relay meter is updated
// might be a bottleneck since ensureRequestAppMetrics is performing multiple
// sequential queries to the Pocket Network node.
// Re-evaluate when caching and invalidation is implemented.
Expand Down Expand Up @@ -166,7 +171,6 @@ func (rmtr *ProxyRelayMeter) AccumulateRelayReward(ctx context.Context, reqMeta
newConsumedCoin := appRelayMeter.consumedCoin.Add(relayCostCoin)

isAppOverServiced := appRelayMeter.maxCoin.IsLT(newConsumedCoin)

if !isAppOverServiced {
appRelayMeter.consumedCoin = newConsumedCoin
return nil
Expand All @@ -180,26 +184,23 @@ func (rmtr *ProxyRelayMeter) AccumulateRelayReward(ctx context.Context, reqMeta
// then return a rate limit error.
overServicingCoin := newConsumedCoin.Sub(appRelayMeter.maxCoin)

// In case Allowance is positive, add it to the maxCoin to allow no or limited over-servicing.
// In case Allowance is positive, add it to maxCoin to allow no or limited over-servicing.
if !allowUnlimitedOverServicing {
maxAllowedOverServicing := appRelayMeter.maxCoin.Add(rmtr.overServicingAllowanceCoins)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could (should?) this be a value that we cache on initialization or new sessions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be no need to caching or initialization, this is the only place where it's being used.

The maxCoin is updated at the beginning of each session, while overServicingAllowanceCoins are configured at startup and unchanged.

if maxAllowedOverServicing.IsLT(newConsumedCoin) {
return ErrRelayerProxyRateLimited.Wrapf(
"application has been rate limited, stake needed: %s, has: %s, ",
"application has been rate limited, stake needed: %s, has: %s",
newConsumedCoin.String(),
appRelayMeter.maxCoin.String(),
)
}
}

appRelayMeter.numOverServicedRelays++
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
numOverServicedRelays := appRelayMeter.numOverServicedRelays

// Exponential backoff: log only when numOverServicedRelays is a power of 2
shouldLog := (numOverServicedRelays & (numOverServicedRelays - 1)) == 0
appRelayMeter.numOverServicedComputeUnits += appRelayMeter.service.ComputeUnitsPerRelay

// Log the over-servicing warning.
if shouldLog {
// Exponential backoff, only log over-servicing when numOverServicedRelays is a power of 2
if shouldLogOverServicing(appRelayMeter.numOverServicedRelays) {
rmtr.logger.Warn().Msgf(
"overservicing enabled, application %q over-serviced %s",
appRelayMeter.app.GetAddress(),
Expand Down Expand Up @@ -230,11 +231,7 @@ func (rmtr *ProxyRelayMeter) SetNonApplicableRelayReward(ctx context.Context, re
sessionRelayMeter.serviceRelayDifficulty,
)
if err != nil {
return err
}

if sessionRelayMeter.numOverServicedRelays > 0 {
return nil
return ErrRelayerProxyUnclaimRelayPrice.Wrapf("%s", err)
}

// Decrease the consumed stake amount by relay cost.
Expand Down Expand Up @@ -437,3 +434,9 @@ func getAppStakePortionPayableToSessionSupplier(

return appStakePerSessionSupplierCoin
}

// shouldLogOverServicing returns true if the number of occurrences is a power of 2.
// This is used to log the over-servicing warning with an exponential backoff.
func shouldLogOverServicing(occurrence uint64) bool {
return (occurrence & (occurrence - 1)) == 0
}
10 changes: 10 additions & 0 deletions x/shared/types/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,13 @@ func GetSettlementSessionEndHeight(sharedParams *Params, queryHeight int64) int6
return GetSessionEndToProofWindowCloseBlocks(sharedParams) +
GetSessionEndHeight(sharedParams, queryHeight) + 1
}

// GetNumPendingSessions returns the number of pending sessions (i.e. that have not
// yet been settled) at the time of queryHeight.
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
func GetNumPendingSessions(sharedParams *Params) int64 {
numBlocksPerSession := int64(sharedParams.GetNumBlocksPerSession())
pendingBlocks := GetSessionEndToProofWindowCloseBlocks(sharedParams)
// numBlocksPerSession - 1 is added to round up the integer division so that pending
// sessions are all the sessions that have their end height at least `pendingBlocks` old
return (pendingBlocks + numBlocksPerSession - 1) / numBlocksPerSession
}
2 changes: 2 additions & 0 deletions x/tokenomics/keeper/settle_pending_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ func (k Keeper) SettlePendingClaims(ctx sdk.Context) (
appAddress := claim.GetSessionHeader().GetApplicationAddress()
applicationInitialStake := applicationInitialStakeMap[appAddress]

// TODO_MAINNET(@red-0ne): Add tests to ensure that a zero application stake
// is handled correctly.
if applicationInitialStake.IsZero() {
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
logger.Error(fmt.Sprintf("application %q has a zero initial stake", appAddress))

Expand Down
19 changes: 10 additions & 9 deletions x/tokenomics/keeper/token_logic_modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,17 @@ func (k Keeper) ProcessTokenLogicModules(

// Ensure the claim amount is within the limits set by Relay Mining.
// If not, update the settlement amount and emit relevant events.
// TODO_MAINNET(@red-0ne): Consider pulling this out of Keeper#ProcessTokenLogicModules
// and ensure claim amount limits are enforced before TLM processing.
actualSettlementCoin, err := k.ensureClaimAmountLimits(ctx, logger, &sharedParams, &application, &supplier, claimSettlementCoin, applicationInitialStake)
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
logger = logger.With("actual_settlement_upokt", actualSettlementCoin)
logger.Info(fmt.Sprintf("About to start processing TLMs for (%d) compute units, equal to (%s) claimed", numClaimComputeUnits, actualSettlementCoin))

// TODO_MAINNET(@red-0ne): Add tests to ensure that a zero settlement coin
// due to integer division rounding is handled correctly.
if actualSettlementCoin.Amount.IsZero() {
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
logger.Warn(fmt.Sprintf(
"actual settlement coin is zero, skipping TLM processing, application %q stake %s",
Expand Down Expand Up @@ -724,22 +728,19 @@ func (k Keeper) ensureClaimAmountLimits(
minRequiredAppStakeAmt := claimSettlementCoin.Amount.Add(globalInflationAmt)
totalClaimedCoin := sdk.NewCoin(volatile.DenomuPOKT, minRequiredAppStakeAmt)

numBlocksPerSession := int64(sharedParams.GetNumBlocksPerSession())
pendingBlocks := sharedtypes.GetSessionEndToProofWindowCloseBlocks(sharedParams)
// We are addding numBlocksPerSession - 1 to round up the integer division
// so that pending sessions are all the sessions that have their end height at least
// `pendingBlocks` old
pendingSessions := (pendingBlocks + numBlocksPerSession - 1) / numBlocksPerSession
// get the number of pending sessions that share the application stake at claim time
// This is used to calculate the maximum claimable amount for the supplier within a session.
numPendingSessions := sharedtypes.GetNumPendingSessions(sharedParams)

// The maximum any single supplier can claim is a fraction of the app's total stake
// divided by the number of suppliers per session.
// Re decentralization - This ensures the app biases towards using all suppliers in a session.
// Re costs - This is an easy way to split the stake evenly.
// TODO_FUTURE: See if there's a way to let the application to pick a single (the best) supplier
// in a session while maintaining a simple solution to implement this.
// TODO_FUTURE: See if there's a way to let the application prefer (the best)
// supplier(s) in a session while maintaining a simple solution to implement this.
maxClaimableAmt := appStake.Amount.
red-0ne marked this conversation as resolved.
Show resolved Hide resolved
Quo(math.NewInt(sessionkeeper.NumSupplierPerSession)).
Quo(math.NewInt(pendingSessions))
Quo(math.NewInt(numPendingSessions))
maxClaimSettlementAmt := supplierAppStakeToMaxSettlementAmount(maxClaimableAmt)

// Check if the claimable amount is capped by the max claimable amount.
Expand Down
Loading