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

feat(sequencer): support IbcActions and implement ICS20 incoming transfer application logic #579

Merged
merged 25 commits into from
Nov 28, 2023

Conversation

noot
Copy link
Collaborator

@noot noot commented Nov 14, 2023

Summary

Adds IBC support for ICS20 token transfers

Background

We want IBC support, specifically for token transfers (eg. using TIA as a gas token).

See the cosmos ICS20 spec: https://github.com/cosmos/ibc/blob/fe150abb629de5c1a598e8c7896a7568f2083681/spec/app/ics-020-fungible-token-transfer/README.md

Changes

  • bump penumbra version and related deps to include IBC refactor for generic AppHandlers + substore support
  • implement an IbcAsset type which represents an IBC-transferred-token
  • update Action proto and native enums to include IbcAction variants, IbcAction is a type defined in penumbra_ibc
  • implement sequencer::accounts::Ics20Transfer which is the app handler for IbcActions, which is called into during IbcAction::check_stateless and IbcAction::execute
  • Ics20Transfer contains the actual ICS20 transfer logic; namely, handling inbound IBC packets that are transfers. there are two main cases we handle for transfer logic: a token being received on Astria from another chain, or a notification of an outbound transfer failing, in which case we need to refund the escrowed tokens.
  • in both cases, we determine if Astria is the source of token we need to issue, and handle the packet accordingly (either minting the synthetic, bridged asset if we are not the source, or sending native Astria tokens from the escrow account to the recipient if we are the source)

Testing

can't really test the transfer functionality without heavier integration testing at the moment, due to the fact that it requires merkle proofs of state of the counterparty chain. in discussions with the penumbra team about a "mock tendermint" test harness we could use to test this in a unit-test-like environment.

Breaking Changelist

  • none

Related Issues

closes #79

@github-actions github-actions bot added proto pertaining to the Astria Protobuf spec sequencer pertaining to the astria-sequencer crate labels Nov 14, 2023
@noot noot changed the title feat(sequencer): suuport IbcActions and implement ICS20 transfer application logic feat(sequencer): support IbcActions and implement ICS20 transfer application logic Nov 16, 2023
SuperFluffy added a commit that referenced this pull request Nov 20, 2023
## Summary
Replaces `buf generate` by `tonic_build` to generate rust code from
protobuf.

## Background
`buf generate` relies on the third party plugins `protoc-gen-prost` and
`protoc-gen-tonic`, which are not yet updated to prost-build 0.12 and
tonic-build 0.10 (and hence prost 0.12, tonic 0.10).

This patch moves code generation away from the `buf generate` executable
to the Rust `tonic-build` crate, because we need to update prost/tonic
and because these projects do not guarantee that code generated with
older versions work with newer versions.

## Changes
- Replace invoking the `buf generate` executable by using `tonic_build`
directly
- Use `buf build` to create a file descriptor set that can be fed into
`tonic_build` (the latter skips its own invocation of `protoc`)
- Clean the generated code by removing all files that are not prefixed
with `astria.` (tonic also creates client and server code for tendermint
abci; I am not sure why, probably due to the descriptor set)

## Testing
Not tested directly; this should not affect the actual code generated.
Everything should still compile, all tests should pass.

## Related Issues
PR requiring a newer version of penumbra and hence prost:
#579

PR againast protoc-gen-prost:
neoeinstein/protoc-gen-prost#78
@@ -21,6 +21,9 @@ use crate::{
state_ext::StateReadExt as _,
};

// TODO: use penumbra's `IBC_SUBSTORE_PREFIX` after they merge #3419
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

if they merge this before this PR is merged (likely), i will update this PR, otherwise i'll do a follow-up.

@noot noot marked this pull request as ready for review November 22, 2023 22:42
Copy link
Member

@SuperFluffy SuperFluffy left a comment

Choose a reason for hiding this comment

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

Just quickly went over this to flag obvious issues, but didn't see anything big outside of implementation specific details.

I am going to do a second round to understand the logic.

@@ -41,6 +41,11 @@ once_cell = "1.17.1"
sha2 = "0.10"
serde = "1"
serde_json = "1"
penumbra-component = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "11d3b524da750c16c376759b03e4bbb3474562d9" }
Copy link
Member

Choose a reason for hiding this comment

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

Do you think we can wait until they make a new release?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

i can ask them when that'll be

Copy link

Choose a reason for hiding this comment

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

@noot The plan is to do a tagged release monday next week (Dec 4th) along with our new persistent testnet deployment

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nice, thanks!

tendermint-config = "0.34"
tendermint-proto = "0.34"
tendermint-rpc = "0.34"
tendermint = "0.34.0"
Copy link
Member

Choose a reason for hiding this comment

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

Why add the .0? Doesn't change anything, just curios.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

i just had this here before i merged your update in and didn't change it lol, so no reason in particular

@@ -25,6 +27,8 @@ tendermint-proto = { workspace = true }
tonic = { workspace = true, optional = true }
tracing = { workspace = true, optional = true }

anyhow = "1"
Copy link
Member

Choose a reason for hiding this comment

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

Please don't use anyhow here but type the errors.

/// # Errors
///
/// - if the denomination string is invalid, ie. does not contain any slashes.
pub fn from_denomination(denom: &str) -> anyhow::Result<Self> {
Copy link
Member

Choose a reason for hiding this comment

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

Can use an impl FromStr here. Also two notes:

  1. you have the Denom type in a previous PR. It is surprising that this is called from_denomation but takes a string. Better try_from_str or just impl FromStr.
  2. astria-proto tries to use from_* for infallible methods, and try_from_* for falllible
  3. Please don't introduce anyhow here. The rest of the lib tries hard to type the errors

@@ -8,6 +8,7 @@ use ed25519_consensus::{
SigningKey,
VerificationKey,
};
use penumbra_ibc::IbcRelay as IbcAction;
Copy link
Member

Choose a reason for hiding this comment

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

Please no alias, just use IbcRelay

// check if escrow account has enough balance to refund user
let balance = state
.get_ibc_channel_balance(source_channel, asset.id())
.await?;
Copy link
Member

Choose a reason for hiding this comment

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

add context

true,
)
.await
.map_err(|e| {
Copy link
Member

Choose a reason for hiding this comment

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

Prefer if let Err(e) = over map_err. In the future we will have https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err

.await
.map_err(|e| {
tracing::error!(
"failed to refund tokens during acknowledge_packet_execute: {}",
Copy link
Member

Choose a reason for hiding this comment

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

Don't format, prefer this (assuming that e is likely an anyhow::Error):

let error: &dyn std::error::Error = e.as_ref();
tracing::error!(error, "failed to <your msg>");

) -> Result<()> {
use prost::Message as _;

let packet_data = FungibleTokenPacketData::decode(data)?;
Copy link
Member

Choose a reason for hiding this comment

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

context for this and the next lines

)
.context("invalid receiver address")?;

if is_source(source_port, source_channel, &asset, is_refund) {
Copy link
Member

Choose a reason for hiding this comment

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

add error context for all the errors in here

Cargo.lock Outdated
Copy link
Member

Choose a reason for hiding this comment

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

pulling some heavy dependencies, wow

@@ -1,7 +1,7 @@
version: v1
name: buf.build/astria/astria
deps:
- buf.build/tendermint/tendermint
- buf.build/penumbra-zone/penumbra
Copy link
Member

Choose a reason for hiding this comment

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

This should pin the specific version of the penumbra proto

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

isn't it pinned in the buf.lock?

Copy link
Member

Choose a reason for hiding this comment

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

Not the same kind of pinning IMO. The lock file encodes code generation at the time buf was run, whereas pinning in the YAML shows author intent.

@noot noot changed the title feat(sequencer): support IbcActions and implement ICS20 transfer application logic feat(sequencer): support IbcActions and implement ICS20 incoming transfer application logic Nov 27, 2023
Copy link
Member

@SuperFluffy SuperFluffy left a comment

Choose a reason for hiding this comment

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

Some comments re making it easier to understand, but looks good. Thank you!

@@ -1,7 +1,7 @@
version: v1
name: buf.build/astria/astria
deps:
- buf.build/tendermint/tendermint
- buf.build/penumbra-zone/penumbra
Copy link
Member

Choose a reason for hiding this comment

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

Not the same kind of pinning IMO. The lock file encodes code generation at the time buf was run, whereas pinning in the YAML shows author intent.

fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidDenomination => {
write!(f, "denomination must contain at least one slash")
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
write!(f, "denomination must contain at least one slash")
f.write_str("denomination must contain at least one slash")

@@ -0,0 +1,349 @@
use std::str::FromStr;
Copy link
Member

Choose a reason for hiding this comment

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

no need to pull this in, use str::parse, see below


let packet_data = FungibleTokenPacketData::decode(data)
.context("failed to decode packet data into FungibleTokenPacketData")?;
let asset = IbcAsset::from_str(&packet_data.denom).context("invalid denomination")?;
Copy link
Member

Choose a reason for hiding this comment

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

Once you have impl'ed FromStr you can use str::parse:

Suggested change
let asset = IbcAsset::from_str(&packet_data.denom).context("invalid denomination")?;
let asset = packet_data.denom.parse::<IbcAsset>().context("failed parsing `denom` field packet data as IbcAsset")?;

.await
.context("failed to get channel balance in refund_tokens_check")?;

let packet_amount: u128 = packet_data.amount.parse()?;
Copy link
Member

Choose a reason for hiding this comment

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

context

Copy link
Member

Choose a reason for hiding this comment

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

Can you add a module doc comment on this explaining (and linking) that the purpose of this module is to implement AppHandlerCheck so that Ics20Transfer can be used for penumbra's check_stateless?

Lots of boiler plate to wire it up and it's tough to understand where the logic comes from.

act.clone()
.with_handler::<crate::accounts::ics20_transfer::Ics20Transfer>()
.check_stateless(())
.await?;
Copy link
Member

Choose a reason for hiding this comment

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

context

act.clone()
.with_handler::<crate::accounts::ics20_transfer::Ics20Transfer>()
.execute(&mut *state)
.await?;
Copy link
Member

Choose a reason for hiding this comment

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

context

Action::Ibc(act) => {
act.clone()
.with_handler::<crate::accounts::ics20_transfer::Ics20Transfer>()
.check_stateless(())
Copy link
Member

Choose a reason for hiding this comment

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

That's their (penumbra's) check_stateless, right?

Yeah, I see the penumbra action handler above. can we make this more obvious to the reader to disambiguate between our check_stateless and theirs?

let the_action = act.clone().with_handler::<Ics20Transfer>();
penumbra_component::ActionHandler::check_stateless(&the_action);

pub(crate) struct Ics20Transfer;

#[async_trait::async_trait]
impl AppHandlerCheck for Ics20Transfer {
Copy link
Member

Choose a reason for hiding this comment

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

So this is where the magic is: AppHandlerCheck must be impl'ed to make with_handler work.

Can we get that as a module doc comment?

@noot noot merged commit b30ce41 into main Nov 28, 2023
26 of 28 checks passed
@noot noot deleted the noot/ibc branch November 28, 2023 17:40
noot added a commit that referenced this pull request Dec 13, 2023
## Summary
Counterpart to #579 which supports incoming ICS20 transfers, this PR
implements outgoing ICS20 transfers.

## Background
required for IBC token transfer support.

See
https://github.com/cosmos/ibc/blob/fe150abb629de5c1a598e8c7896a7568f2083681/spec/app/ics-020-fungible-token-transfer/README.md
`sendFungibleTokens` for spec.

## Changes
- add `Ics20Withdrawal` proto and native type which is an action (maybe
rename to `Ics20WithdrawalAction`?)
- implement check logic (apart from IBC packet check, check the sender
has enough balance)
- implement execution logic (escrow tokens if native, or burn if not
native)

## Testing
not really - same issue as #579 

## Related Issues

closes #597
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proto pertaining to the Astria Protobuf spec sequencer pertaining to the astria-sequencer crate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

IBC Bridging between Celestia and Astria Sequencer
3 participants