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

[Uptime Incentives]: Add authorized uptime validation for new NoLock gauges #7376

Merged
merged 8 commits into from
Jan 30, 2024

Conversation

AlpinYukseloglu
Copy link
Contributor

@AlpinYukseloglu AlpinYukseloglu commented Jan 28, 2024

Closes: #7367

What is the purpose of the change

This PR adds the relevant validation logic for authorized uptimes on CL gauge durations. Given how this logic touches existing state and a heavily depended on component of our codebase, the changes have heavy comments explaining the reasoning and implications.

This PR also makes a change to how CL gauges are linked to pool IDs in state. It is important to note that this breaks the previous setup where there was a "general linking" and then an additional link for internal gauges, reducing this to a single link based on gauge duration. The implications and next steps to ensure the safety of this are outlined in great detail in #7375.

Testing and Verifying

Tests covering the validation logic are added to gauge_test.go. The key changes are covered by existing tests, which were how the issue was discovered in the first place.

Documentation and Release Note

  • Does this pull request introduce a new feature or user-facing behavior changes?
  • Changelog entry added to Unreleased section of CHANGELOG.md?

Where is the change documented?

  • Specification (x/{module}/README.md)
  • Osmosis documentation site
  • Code comments?
  • N/A

// Assert that pool id and gauge id link meant for internally incentivized gauges is unset.
_, err := s.App.PoolIncentivesKeeper.GetPoolGaugeId(s.Ctx, tc.poolId, tc.distrTo.Duration)
s.Require().Error(err)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Highlighting this for review @p0mvn. The key-related change in this PR means that the "general linking" between gauges and pools is now based on duration, which means that this check is no longer necessary.

It also technically means that we can skip the "internal gauge link" component of the internal gauge setup, but that logic is not well abstracted so it would require meaningful additional work to save on the duplicate write that would be happening here.

@AlpinYukseloglu AlpinYukseloglu added the V:state/breaking State machine breaking PR label Jan 28, 2024
@@ -1139,6 +1139,7 @@ func (s *KeeperTestSuite) CreateNoLockExternalGauges(clPoolId uint64, externalGa
clPoolExternalGaugeId, err := s.App.IncentivesKeeper.CreateGauge(s.Ctx, numEpochsPaidOver == 1, gaugeCreator, externalGaugeCoins,
lockuptypes.QueryCondition{
LockQueryType: lockuptypes.NoLock,
Duration: time.Nanosecond,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note to reviewer: CL gauges with unauthorized uptimes can no longer be created due to the new validation logic, so existing tests are updated to use 1ns which is authorized by default. See #7375 for discussion on backwards compatibility with gauges currently live on mainnet with unauthorized uptimes.

Copy link
Member

@p0mvn p0mvn left a comment

Choose a reason for hiding this comment

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

Nice work! LGTM so far.

I will aim to review replies and run tests more before approving later today or whenever ready

x/incentives/keeper/gauge.go Outdated Show resolved Hide resolved
x/incentives/keeper/gauge.go Outdated Show resolved Hide resolved
x/incentives/keeper/gauge.go Outdated Show resolved Hide resolved
x/pool-incentives/keeper/grpc_query_test.go Outdated Show resolved Hide resolved
Copy link
Member

@p0mvn p0mvn left a comment

Choose a reason for hiding this comment

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

LGTM.

I had some recommendations for making the execution flow stricter but not a blocker

x/incentives/keeper/gauge.go Outdated Show resolved Hide resolved
Comment on lines 151 to 161
if isExternalConcentratedPoolGauge {
durations = k.clk.GetParams(ctx).AuthorizedUptimes
} else if isInternalConcentratedPoolGauge {
// Internal CL gauges use epoch time as their duration. This is a legacy
// property that does not affect the uptime on created records, which is
// determined by the gov param for internal incentive uptimes.
durations = []time.Duration{k.GetEpochInfo(ctx).Duration}
} else {
// This branch is applicable to CFMM pool types such as balancer and stableswap.
durations = k.GetLockableDurations(ctx)
}
Copy link
Member

Choose a reason for hiding this comment

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

This would make the conditional branching stricter and eliminate the likelihood of invalid field setup causing issues.

nit: the else branch is also not covered by a test at the moment and might benefit from it

Suggested change
if isExternalConcentratedPoolGauge {
durations = k.clk.GetParams(ctx).AuthorizedUptimes
} else if isInternalConcentratedPoolGauge {
// Internal CL gauges use epoch time as their duration. This is a legacy
// property that does not affect the uptime on created records, which is
// determined by the gov param for internal incentive uptimes.
durations = []time.Duration{k.GetEpochInfo(ctx).Duration}
} else {
// This branch is applicable to CFMM pool types such as balancer and stableswap.
durations = k.GetLockableDurations(ctx)
}
if isExternalConcentratedPoolGauge {
durations = k.clk.GetParams(ctx).AuthorizedUptimes
} else if isInternalConcentratedPoolGauge {
// Internal CL gauges use epoch time as their duration. This is a legacy
// property that does not affect the uptime on created records, which is
// determined by the gov param for internal incentive uptimes.
durations = []time.Duration{k.GetEpochInfo(ctx).Duration}
} else if !isConcentratedPoolGauge {
// This branch is applicable to CFMM pool types such as balancer and stableswap.
durations = k.GetLockableDurations(ctx)
} else {
return 0, fmt.Errorf("invalid gauge type: %s", distrTo.LockQueryType)
}

Copy link
Contributor Author

@AlpinYukseloglu AlpinYukseloglu Jan 29, 2024

Choose a reason for hiding this comment

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

I see what you're getting at here, but this flow is not intended to serve as a check or filter where we return an error. For example, your implementation would error if CreateGauge is called for a Group gauge, which is not what we want here.

It does seem as though this logic is somewhat unclear though. I moved this check into the block where the duration checks happen, as that probably makes it clearer (i.e. if NoLock gauge || ByDuration gauge, run this logic implies that we are covering everything we need to with the logic branches in the original implementation). Hopefully this makes it a bit clearer.

Please let me know if I misunderstood your suggestion

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -28,7 +28,7 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState *types.GenesisState) {
}
if genState.ConcentratedPoolToNoLockGauges != nil {
for _, record := range genState.ConcentratedPoolToNoLockGauges.PoolToGauge {
k.SetPoolGaugeIdNoLock(ctx, record.PoolId, record.GaugeId)
k.SetPoolGaugeIdNoLock(ctx, record.PoolId, record.GaugeId, record.Duration)
Copy link
Member

Choose a reason for hiding this comment

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

Are we planning for any migration PR after this? 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We shouldn't need to - please see #7375 (if you see any problems with the reasoning in that issue please flag them!)

@@ -507,7 +507,8 @@ func (s *KeeperTestSuite) TestCreateGauge_NoLockGauges() {
distrTo: lockuptypes.QueryCondition{
LockQueryType: lockuptypes.NoLock,
// Note: this assumes the gauge is external
Denom: "",
Denom: "",
Copy link
Member

Choose a reason for hiding this comment

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

Can we also add non-failing cases for the three branches that has been added to nolock createGauge?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The non-failing case should be covered by existing tests, but will re review to make sure I'm not missing anything

Copy link
Member

Choose a reason for hiding this comment

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

Sg! On first site of existing tests, test cases did not seem obvious in which case touches which branch. Please feel free to merge once you have re-checked!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right – since 1 nanosecond is an authorized uptime by default, the test cases with that duration should be covering the non-failing branches

Copy link
Member

@czarcas7ic czarcas7ic left a comment

Choose a reason for hiding this comment

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

This should already be addressed in #7369, but if it isn’t yet, an invalid or unauthorized gauge duration should fall back to the default uptime of 1ns in this line (instead of directly using the gauge’s duration):

I just had a question about this statement. Why should we fall back in this case? Shouldn't we be able to control this so that distribute never comes across an invalid or unauthorized duration?

@AlpinYukseloglu
Copy link
Contributor Author

I just had a question about this statement. Why should we fall back in this case? Shouldn't we be able to control this so that distribute never comes across an invalid or unauthorized duration?

#7369 has a breakdown of the reasoning. Tldr is this 1. minimizes the surface for existing gauges to unintentionally trigger chain halts/block incentives, and 2. is a clever way for us to ensure uptimes can be "unauthorized" in the future.

There are probably marginal gains we can make on these fronts with a meaningfully more complex implementation, but this approach seems quite elegant (and secure) to me given the current tradeoff space. As always, feel free to push back!

@AlpinYukseloglu AlpinYukseloglu merged commit 14078fe into main Jan 30, 2024
1 check passed
@AlpinYukseloglu AlpinYukseloglu deleted the alpo/gauge-uptime-validation branch January 30, 2024 21:17
p0mvn pushed a commit that referenced this pull request Feb 8, 2024
…gauges (#7376)

* add uptime validation logic and fix pool gauge link

* add tests and clean up implementation

* add changelog and clean up prints/diff

* update changelog entry to be clearer

* clean up comments and add additional tests

* fix broken genesis tests

* extract default NoLock duration to variable and add further comments

* switch naming from concentrated to NoLock and move duration setup into appropriate logic branch
@github-actions github-actions bot mentioned this pull request Mar 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Uptime Incentives]: Add authorized uptime validation for new NoLock gauges
4 participants