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

IBC Rate Limit - integration and tests #2410

Closed
wants to merge 246 commits into from

Conversation

nicolaslara
Copy link
Contributor

Closes: #2305

What is the purpose of the change

Integrates IBC rate limiting with the chain.

Brief Changelog

  • An IBC middleware is now provided and registered with the IBC transfer module.
  • The level of rate limiting is controlled by a rate limiting contract that can be controlled by an address specified by governance (this could be the gov module itself)
  • The "governance address" of the rate limiting contract can reset the quota when a rate limit is hit (this could be the gov module itself)

Testing and Verifying

This change added tests and can be verified as follows:

  • Added integration tests for the middleware (IBC Message <-> IBC transfer module <-> RateLimitingMiddlware <-> Relayer <-> Counterparty chain) under x/ibc-rate-limit
  • Added tests to the contract (see IBC Rate limiting contract #2408)

Documentation and Release Note

This PR depends on @2408 and @2409. It also requires a change to the osmosis fork of the cosmos-sdk (#2307)

  • Does this pull request introduce a new feature or user-facing behavior changes? (yes)
    • If the rate limit is reached, IBC transactions will be blocked for users.
  • Is a relevant changelog entry added to the Unreleased section in CHANGELOG.md? (no)
    • Should I add this?
  • How is the feature or change documented? (not applicable / specification (x/<module>/spec/) / Osmosis docs repo / not documented)
    • Currently undocummented. Should I add docs for this? what is the right place for that?

@nicolaslara
Copy link
Contributor Author

Created #2941 to add the testing helpers needed by this branch. That one should be a quick merge and make this PR smaller

@nicolaslara nicolaslara added the V:state/breaking State machine breaking PR label Oct 10, 2022
@ancazamfir
Copy link

Hi, i was looking at the osmosis rate limiter code and have some questions. Maybe this PR is not the best to ask but will do anyway and please redirect. On

amount, denom, err := GetFundsFromPacket(packet)
if err != nil {
return channeltypes.NewErrorAcknowledgement("bad packet")
}
channelValue := im.ics4Middleware.CalculateChannelValue(ctx, denom)

GetFundsFromPacket gets the denom as specified by the sender. On receive side (in the code above) shouldn't there be un-prefexing or prefexing performed depending on wether the receiving chain was the one originally sending the packet or not? Similar thing is done in the transfer module https://github.com/cosmos/ibc-go/blob/f693f6c11053192f369b5908c9186bd494c3a33d/modules/apps/transfer/keeper/relay.go#L165

The way it is the channel value is computed for a denom of the counterparty/ sender chain. Or maybe I am missing something important here.

Comment on lines 86 to 92
coins = transfertypes.GetTransferCoin(
suite.path.EndpointB.ChannelConfig.PortID,
suite.path.EndpointB.ChannelID,
sdk.DefaultBondDenom,
sdk.NewInt(1),
)
coins = sdk.NewCoin(sdk.DefaultBondDenom, amount)

Choose a reason for hiding this comment

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

why the overwrite and not:

Suggested change
coins = transfertypes.GetTransferCoin(
suite.path.EndpointB.ChannelConfig.PortID,
suite.path.EndpointB.ChannelID,
sdk.DefaultBondDenom,
sdk.NewInt(1),
)
coins = sdk.NewCoin(sdk.DefaultBondDenom, amount)
coins = transfertypes.GetTransferCoin(
suite.path.EndpointB.ChannelConfig.PortID,
suite.path.EndpointB.ChannelID,
sdk.DefaultBondDenom,
amount,
)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This got overlooked. Thanks for the catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This probably also means that you're other comment is also right. I'll review this and report back

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 correct code here is coins = sdk.NewCoin(sdk.DefaultBondDenom, amount) because that message is sent from chainB to chainA. What you said about getting the funds still applies though: I was not wrapping the token before getting the value and this was only working because both chains in my tests were the same chain.

I've fixed that in my latest commit (032f33b)

Choose a reason for hiding this comment

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

The correct code here is coins = sdk.NewCoin(sdk.DefaultBondDenom, amount) because that message is sent from chainB to chainA.

I am not sure I understand TestRecvTransferWithRateLimiting() (which calls this with forward = false). The tokens sent back to A should be prefixed (something like "ibc/transfer/channel-0/stake"...can't remember the details). Will look at the test latest tomorrow (running out of time today).

What you said about getting the funds still applies though: I was not wrapping the token before getting the value and this was only working because both chains in my tests were the same chain.

I've fixed that in my latest commit (032f33b)

Looks good!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, this is testing native B tokens being sent to a. But maybe I should also add a test for native A tokens being sent "back" from B to A

@nicolaslara
Copy link
Contributor Author

Modified this implementation to use the escrowed amount as the channel value for native tokens. This introduces a bootstrapping problem: channels need to have tokens on escrow before we initialize their quotas (otherwise the quota will always be lower than the sent amount). This can be seen in the tests.

I don't think this is a problem for our use case as we will only use rate limiting for existing channels, but it may become important if someone wants all channels to have rate limiting, or to auto-initialize rate-limiting for channels on creation

@ancazamfir
Copy link

Modified this implementation to use the escrowed amount as the channel value for native tokens. This introduces a bootstrapping problem: channels need to have tokens on escrow before we initialize their quotas (otherwise the quota will always be lower than the sent amount). This can be seen in the tests.

I don't think this is a problem for our use case as we will only use rate limiting for existing channels, but it may become important if someone wants all channels to have rate limiting, or to auto-initialize rate-limiting for channels on creation

Right. Another possibility is to set some threshold for which the ratelimiter activates/ deactivates.

Comment on lines +94 to +101
func (i *ICS4Wrapper) CalculateChannelValue(ctx sdk.Context, denom string, packet exported.PacketI) sdk.Int {
// If the source is the counterparty chain, this should be
if strings.HasPrefix(denom, "ibc/") {
return i.bankKeeper.GetSupplyWithOffset(ctx, denom).Amount
}

escrowAddress := transfertypes.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel())
return i.bankKeeper.GetBalance(ctx, escrowAddress, denom).Amount

Choose a reason for hiding this comment

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

At first look this doesn't seem correct. If we have something like:

 A            B              C
ch0  --- ch1    ch2 --- ch3

And a is chain A token. In the following cases:

  1. B sends to C a packet with denom ch1/a over ch2. denom here is ch1/a.
  2. B receives (back) from C a packet with denom ch3/ch1/a over ch2. denom here is ch1/a (unprefixed)

Here I think we get the ch1/a supply instead of the balance of escrow_address(ch2) for denom ch1/a.

Maybe something along these lines:

Suggested change
func (i *ICS4Wrapper) CalculateChannelValue(ctx sdk.Context, denom string, packet exported.PacketI) sdk.Int {
// If the source is the counterparty chain, this should be
if strings.HasPrefix(denom, "ibc/") {
return i.bankKeeper.GetSupplyWithOffset(ctx, denom).Amount
}
escrowAddress := transfertypes.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel())
return i.bankKeeper.GetBalance(ctx, escrowAddress, denom).Amount
func (i *ICS4Wrapper) CalculateChannelValue(ctx sdk.Context, denom string, packet exported.PacketI, send bool) sdk.Int {
prefix_for_source:= transfertypes.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel())
if send { // send packet
if !strings.HasPrefix(denom, prefix_for_source) {
// Sender chain is the source
escrowAddress := transfertypes.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel())
return i.bankKeeper.GetBalance(ctx, escrowAddress, denom).Amount
} else {
return i.bankKeeper.GetSupplyWithOffset(ctx, denom).Amount
}
} else { // receive packet
if strings.HasPrefix(denom, prefix_for_source) {
// Receiver chain is the source
escrowAddress := transfertypes.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel())
return i.bankKeeper.GetBalance(ctx, escrowAddress, denom).Amount
} else {
return i.bankKeeper.GetSupplyWithOffset(ctx, denom).Amount
}
}

It would be great to have tests to cover scenarios like above.

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 current behaviour (what you described above) is what I was trying to achieve, where escrow value is only used for native tokens (osmo/ion/tokenfactory tokens). ch0/a, as an ibc token, should be rate limited based on the current amount of ch0/a on osmosis.

I'm open to changing it to what you suggest, but I think the volume of some token/channel combinations may be too low and make the limit to be hit too often

Choose a reason for hiding this comment

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

The current behaviour (what you described above) is what I was trying to achieve, where escrow value is only used for native tokens (osmo/ion/tokenfactory tokens). ch0/a, as an ibc token, should be rate limited based on the current amount of ch0/a on osmosis.

I see, thanks for the clarification.

I'm open to changing it to what you suggest, but I think the volume of some token/channel combinations may be too low and make the limit to be hit too often

But in this case ratelimiting should not be configured on those channels as they have a low value.
Are there osmosis uscases like the ones I mention above (the multi hop/channel transfers)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C:app-wiring Changes to the app folder T:CI V:state/breaking State machine breaking PR
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

IBC Rate Limiting
3 participants