Skip to content

Commit fecfaee

Browse files
committed
Merge branch 'yuji/fix-ibc-shielding-transfer' (#3444)
* origin/yuji/fix-ibc-shielding-transfer: remove --refund extract_memo_from_packet fix refund source update Hermes IbcShieldingData add changelog add IbcShielding action workaround wasm compilation error fix gen_masp_tx in test fix e2e tests add CLI ibc-gen-shielding extract masp_tx in MASP VP memo for masp tx fix the port and channel for is_receiving_success fix tests fix convert_to_address add ibc trace file reduce assumptions for IBC VP refactoring: add trace.rs fix for apply_recv_msg support NftTransfer
2 parents 71ce06f + 7212f7e commit fecfaee

File tree

39 files changed

+1064
-732
lines changed

39 files changed

+1064
-732
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Fix IBC shielding transfer for the receiver not to be replaced by a malicious
2+
relayer ([\#3438](https://github.com/anoma/namada/issues/3438))
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.8.2-namada-beta12-rc6
1+
1.9.0-namada-beta13-rc2

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/apps_lib/src/cli.rs

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ pub mod cmds {
298298
// Actions
299299
.subcommand(SignTx::def().display_order(6))
300300
.subcommand(ShieldedSync::def().display_order(6))
301+
.subcommand(GenIbcShieldingTransfer::def().display_order(6))
301302
// Utils
302303
.subcommand(ClientUtils::def().display_order(7))
303304
}
@@ -386,6 +387,8 @@ pub mod cmds {
386387
Self::parse_with_ctx(matches, AddToEthBridgePool);
387388
let sign_tx = Self::parse_with_ctx(matches, SignTx);
388389
let shielded_sync = Self::parse_with_ctx(matches, ShieldedSync);
390+
let gen_ibc_shielding =
391+
Self::parse_with_ctx(matches, GenIbcShieldingTransfer);
389392
let utils = SubCmd::parse(matches).map(Self::WithoutContext);
390393
tx_custom
391394
.or(tx_transparent_transfer)
@@ -440,6 +443,7 @@ pub mod cmds {
440443
.or(query_account)
441444
.or(sign_tx)
442445
.or(shielded_sync)
446+
.or(gen_ibc_shielding)
443447
.or(utils)
444448
}
445449
}
@@ -530,6 +534,7 @@ pub mod cmds {
530534
QueryRewards(QueryRewards),
531535
SignTx(SignTx),
532536
ShieldedSync(ShieldedSync),
537+
GenIbcShieldingTransfer(GenIbcShieldingTransfer),
533538
}
534539

535540
#[allow(clippy::large_enum_variant)]
@@ -2310,6 +2315,29 @@ pub mod cmds {
23102315
}
23112316
}
23122317

2318+
#[derive(Clone, Debug)]
2319+
pub struct GenIbcShieldingTransfer(
2320+
pub args::GenIbcShieldingTransfer<args::CliTypes>,
2321+
);
2322+
2323+
impl SubCmd for GenIbcShieldingTransfer {
2324+
const CMD: &'static str = "ibc-gen-shielding";
2325+
2326+
fn parse(matches: &ArgMatches) -> Option<Self> {
2327+
matches.subcommand_matches(Self::CMD).map(|matches| {
2328+
GenIbcShieldingTransfer(args::GenIbcShieldingTransfer::parse(
2329+
matches,
2330+
))
2331+
})
2332+
}
2333+
2334+
fn def() -> App {
2335+
App::new(Self::CMD)
2336+
.about("Generate shielding transfer for IBC.")
2337+
.add_args::<args::GenIbcShieldingTransfer<args::CliTypes>>()
2338+
}
2339+
}
2340+
23132341
#[derive(Clone, Debug)]
23142342
pub struct EpochSleep(pub args::Query<args::CliTypes>);
23152343

@@ -3373,7 +3401,6 @@ pub mod args {
33733401
pub const RAW_PUBLIC_KEY_HASH_OPT: ArgOpt<String> =
33743402
RAW_PUBLIC_KEY_HASH.opt();
33753403
pub const RECEIVER: Arg<String> = arg("receiver");
3376-
pub const REFUND: ArgFlag = flag("refund");
33773404
pub const REFUND_TARGET: ArgOpt<WalletTransferTarget> =
33783405
arg_opt("refund-target");
33793406
pub const RELAYER: Arg<Address> = arg("relayer");
@@ -6616,32 +6643,31 @@ pub mod args {
66166643
}
66176644
}
66186645

6619-
impl CliToSdk<GenIbcShieldedTransfer<SdkTypes>>
6620-
for GenIbcShieldedTransfer<CliTypes>
6646+
impl CliToSdk<GenIbcShieldingTransfer<SdkTypes>>
6647+
for GenIbcShieldingTransfer<CliTypes>
66216648
{
66226649
type Error = std::convert::Infallible;
66236650

66246651
fn to_sdk(
66256652
self,
66266653
ctx: &mut Context,
6627-
) -> Result<GenIbcShieldedTransfer<SdkTypes>, Self::Error> {
6654+
) -> Result<GenIbcShieldingTransfer<SdkTypes>, Self::Error> {
66286655
let query = self.query.to_sdk(ctx)?;
66296656
let chain_ctx = ctx.borrow_chain_or_exit();
66306657

6631-
Ok(GenIbcShieldedTransfer::<SdkTypes> {
6658+
Ok(GenIbcShieldingTransfer::<SdkTypes> {
66326659
query,
66336660
output_folder: self.output_folder,
66346661
target: chain_ctx.get(&self.target),
66356662
token: self.token,
66366663
amount: self.amount,
66376664
port_id: self.port_id,
66386665
channel_id: self.channel_id,
6639-
refund: self.refund,
66406666
})
66416667
}
66426668
}
66436669

6644-
impl Args for GenIbcShieldedTransfer<CliTypes> {
6670+
impl Args for GenIbcShieldingTransfer<CliTypes> {
66456671
fn parse(matches: &ArgMatches) -> Self {
66466672
let query = Query::parse(matches);
66476673
let output_folder = OUTPUT_FOLDER_PATH.parse(matches);
@@ -6650,7 +6676,6 @@ pub mod args {
66506676
let amount = InputAmount::Unvalidated(AMOUNT.parse(matches));
66516677
let port_id = PORT_ID.parse(matches);
66526678
let channel_id = CHANNEL_ID.parse(matches);
6653-
let refund = REFUND.parse(matches);
66546679
Self {
66556680
query,
66566681
output_folder,
@@ -6659,7 +6684,6 @@ pub mod args {
66596684
amount,
66606685
port_id,
66616686
channel_id,
6662-
refund,
66636687
}
66646688
}
66656689

@@ -6681,9 +6705,6 @@ pub mod args {
66816705
.arg(CHANNEL_ID.def().help(wrap!(
66826706
"The channel ID via which the token is received."
66836707
)))
6684-
.arg(REFUND.def().help(wrap!(
6685-
"Generate the shielded transfer for refunding."
6686-
)))
66876708
}
66886709
}
66896710

crates/apps_lib/src/cli/client.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,20 @@ impl CliApi {
370370
)
371371
.await?;
372372
}
373+
Sub::GenIbcShieldingTransfer(GenIbcShieldingTransfer(
374+
args,
375+
)) => {
376+
let chain_ctx = ctx.borrow_mut_chain_or_exit();
377+
let ledger_address =
378+
chain_ctx.get(&args.query.ledger_address);
379+
let client = client.unwrap_or_else(|| {
380+
C::from_tendermint_address(&ledger_address)
381+
});
382+
client.wait_until_node_is_synced(&io).await?;
383+
let args = args.to_sdk(&mut ctx)?;
384+
let namada = ctx.to_sdk(client, io);
385+
tx::gen_ibc_shielding_transfer(&namada, args).await?;
386+
}
373387
#[cfg(feature = "namada-eth-bridge")]
374388
Sub::AddToEthBridgePool(args) => {
375389
let args = args.0;

crates/apps_lib/src/cli/context.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ use namada::core::chain::ChainId;
1111
use namada::core::ethereum_events::EthAddress;
1212
use namada::core::key::*;
1313
use namada::core::masp::*;
14-
use namada::ibc::{is_ibc_denom, is_nft_trace};
14+
use namada::ibc::trace::{ibc_token, is_ibc_denom, is_nft_trace};
1515
use namada::io::Io;
16-
use namada::ledger::ibc::storage::ibc_token;
1716
use namada_sdk::masp::fs::FsShieldedUtils;
1817
use namada_sdk::masp::ShieldedContext;
1918
use namada_sdk::wallet::Wallet;

crates/apps_lib/src/client/tx.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::fs::File;
2+
use std::io::Write;
23

34
use borsh::BorshDeserialize;
45
use borsh_ext::BorshSerializeExt;
@@ -11,6 +12,7 @@ use namada::core::key::*;
1112
use namada::governance::cli::onchain::{
1213
DefaultProposal, PgfFundingProposal, PgfStewardProposal,
1314
};
15+
use namada::ibc::convert_masp_tx_to_ibc_memo;
1416
use namada::io::Io;
1517
use namada::state::EPOCH_SWITCH_BLOCKS_DELAY;
1618
use namada::tx::data::compute_inner_tx_hash;
@@ -1395,3 +1397,32 @@ pub async fn submit_tx(
13951397
) -> Result<TxResponse, error::Error> {
13961398
tx::submit_tx(namada, to_broadcast).await
13971399
}
1400+
1401+
/// Generate MASP transaction and output it
1402+
pub async fn gen_ibc_shielding_transfer(
1403+
context: &impl Namada,
1404+
args: args::GenIbcShieldingTransfer,
1405+
) -> Result<(), error::Error> {
1406+
if let Some(masp_tx) =
1407+
tx::gen_ibc_shielding_transfer(context, args.clone()).await?
1408+
{
1409+
let tx_id = masp_tx.txid().to_string();
1410+
let filename = format!("ibc_masp_tx_{}.memo", tx_id);
1411+
let output_path = match &args.output_folder {
1412+
Some(path) => path.join(filename),
1413+
None => filename.into(),
1414+
};
1415+
let mut out = File::create(&output_path)
1416+
.expect("Creating a new file for IBC MASP transaction failed.");
1417+
let bytes = convert_masp_tx_to_ibc_memo(&masp_tx);
1418+
out.write_all(bytes.as_bytes())
1419+
.expect("Writing IBC MASP transaction file failed.");
1420+
println!(
1421+
"Output IBC shielding transfer for {tx_id} to {}",
1422+
output_path.to_string_lossy()
1423+
);
1424+
} else {
1425+
eprintln!("No shielded transfer for this IBC transfer.")
1426+
}
1427+
Ok(())
1428+
}

crates/ibc/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namada_storage = { path = "../storage" }
3232
namada_token = { path = "../token" }
3333

3434
borsh.workspace = true
35+
data-encoding.workspace = true
3536
konst.workspace = true
3637
linkme = {workspace = true, optional = true}
3738
ibc.workspace = true
@@ -45,6 +46,7 @@ prost.workspace = true
4546
serde.workspace = true
4647
serde_json.workspace = true
4748
sha2.workspace = true
49+
smooth-operator.workspace = true
4850
thiserror.workspace = true
4951
tracing.workspace = true
5052

crates/ibc/src/context/common.rs

Lines changed: 25 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use ibc::core::channel::types::commitment::{
99
};
1010
use ibc::core::channel::types::error::{ChannelError, PacketError};
1111
use ibc::core::channel::types::packet::Receipt;
12-
use ibc::core::channel::types::timeout::TimeoutHeight;
1312
use ibc::core::client::types::error::ClientError;
1413
use ibc::core::client::types::Height;
1514
use ibc::core::connection::types::error::ConnectionError;
@@ -23,14 +22,14 @@ use ibc::primitives::Timestamp;
2322
use namada_core::address::Address;
2423
use namada_core::storage::{BlockHeight, Key};
2524
use namada_core::tendermint::Time as TmTime;
25+
use namada_storage::{Error as StorageError, StorageRead};
2626
use namada_token::storage_key::balance_key;
2727
use namada_token::Amount;
2828
use prost::Message;
29-
use sha2::Digest;
3029

3130
use super::client::{AnyClientState, AnyConsensusState};
3231
use super::storage::IbcStorageContext;
33-
use crate::{storage, NftClass, NftMetadata};
32+
use crate::{storage, trace, NftClass, NftMetadata};
3433

3534
/// Result of IBC common function call
3635
pub type Result<T> = std::result::Result<T, ContextError>;
@@ -408,7 +407,7 @@ pub trait IbcCommonContext: IbcStorageContext {
408407
channel_id: &ChannelId,
409408
) -> Result<Sequence> {
410409
let key = storage::next_sequence_send_key(port_id, channel_id);
411-
self.read_sequence(&key)
410+
read_sequence(self, &key).map_err(ContextError::from)
412411
}
413412

414413
/// Store the NextSequenceSend
@@ -429,7 +428,7 @@ pub trait IbcCommonContext: IbcStorageContext {
429428
channel_id: &ChannelId,
430429
) -> Result<Sequence> {
431430
let key = storage::next_sequence_recv_key(port_id, channel_id);
432-
self.read_sequence(&key)
431+
read_sequence(self, &key).map_err(ContextError::from)
433432
}
434433

435434
/// Store the NextSequenceRecv
@@ -450,7 +449,7 @@ pub trait IbcCommonContext: IbcStorageContext {
450449
channel_id: &ChannelId,
451450
) -> Result<Sequence> {
452451
let key = storage::next_sequence_ack_key(port_id, channel_id);
453-
self.read_sequence(&key)
452+
read_sequence(self, &key).map_err(ContextError::from)
454453
}
455454

456455
/// Store the NextSequenceAck
@@ -464,57 +463,12 @@ pub trait IbcCommonContext: IbcStorageContext {
464463
self.store_sequence(&key, seq)
465464
}
466465

467-
/// Read a sequence
468-
fn read_sequence(&self, key: &Key) -> Result<Sequence> {
469-
match self.read_bytes(key)? {
470-
Some(value) => {
471-
let value: [u8; 8] =
472-
value.try_into().map_err(|_| ChannelError::Other {
473-
description: format!(
474-
"The sequence value wasn't u64: Key {key}",
475-
),
476-
})?;
477-
Ok(u64::from_be_bytes(value).into())
478-
}
479-
// when the sequence has never been used, returns the initial value
480-
None => Ok(1.into()),
481-
}
482-
}
483-
484466
/// Store the sequence
485467
fn store_sequence(&mut self, key: &Key, sequence: Sequence) -> Result<()> {
486468
let bytes = u64::from(sequence).to_be_bytes().to_vec();
487469
self.write_bytes(key, bytes).map_err(ContextError::from)
488470
}
489471

490-
/// Calculate the hash
491-
fn hash(value: &[u8]) -> Vec<u8> {
492-
sha2::Sha256::digest(value).to_vec()
493-
}
494-
495-
/// Calculate the packet commitment
496-
fn compute_packet_commitment(
497-
packet_data: &[u8],
498-
timeout_height: &TimeoutHeight,
499-
timeout_timestamp: &Timestamp,
500-
) -> PacketCommitment {
501-
let mut hash_input =
502-
timeout_timestamp.nanoseconds().to_be_bytes().to_vec();
503-
504-
let revision_number =
505-
timeout_height.commitment_revision_number().to_be_bytes();
506-
hash_input.append(&mut revision_number.to_vec());
507-
508-
let revision_height =
509-
timeout_height.commitment_revision_height().to_be_bytes();
510-
hash_input.append(&mut revision_height.to_vec());
511-
512-
let packet_data_hash = Self::hash(packet_data);
513-
hash_input.append(&mut packet_data_hash.to_vec());
514-
515-
Self::hash(&hash_input).into()
516-
}
517-
518472
/// Get the packet commitment
519473
fn packet_commitment(
520474
&self,
@@ -703,7 +657,7 @@ pub trait IbcCommonContext: IbcStorageContext {
703657
token_id: &TokenId,
704658
owner: &Address,
705659
) -> Result<bool> {
706-
let ibc_token = storage::ibc_token_for_nft(class_id, token_id);
660+
let ibc_token = trace::ibc_token_for_nft(class_id, token_id);
707661
let balance_key = balance_key(&ibc_token, owner);
708662
let amount = self.read::<Amount>(&balance_key)?;
709663
Ok(amount == Some(Amount::from_u64(1)))
@@ -753,3 +707,22 @@ pub trait IbcCommonContext: IbcStorageContext {
753707
self.write(&key, amount).map_err(ContextError::from)
754708
}
755709
}
710+
711+
/// Read and decode the IBC sequence
712+
pub fn read_sequence<S: StorageRead + ?Sized>(
713+
storage: &S,
714+
key: &Key,
715+
) -> std::result::Result<Sequence, StorageError> {
716+
match storage.read_bytes(key)? {
717+
Some(value) => {
718+
let value: [u8; 8] = value.try_into().map_err(|_| {
719+
StorageError::new_alloc(format!(
720+
"The sequence value wasn't u64: Key {key}",
721+
))
722+
})?;
723+
Ok(u64::from_be_bytes(value).into())
724+
}
725+
// when the sequence has never been used, returns the initial value
726+
None => Ok(1.into()),
727+
}
728+
}

0 commit comments

Comments
 (0)